传统的异常处理
public class Test01 { public static void main(String[] args) { Scanner sc = new Scanner(System.in); System.out.println("请输入被除数:"); int num1, num2; if (sc.hasNextInt()) { num1 = sc.nextInt(); System.out.println("请输入除数:"); if (sc.hasNextInt()) { num2 = sc.nextInt(); if (0 == num2) { System.out.println("除数不能为0"); } else { int r = num1 / num2; System.out.println("r=" + r); } } else { System.out.println("除数输入有误!"); } } else { System.out.println("被除数输入有误!"); } } }
传统异常处理是通过if条件判断来处理的:
[1] 效率极低
[2] 业务逻辑和异常处理交织在一起,维护性差。
异常处理机制
异常的概念
异常是指在程序的运行过程中所发生的不正常的事件,它会中断正在运行的程序
通过异常处理机制,我们不能过度使用异常。
异常处理
try-catch
把可能产生异常的代码放入try代码块中。如果没有产生异常,程序不会执行catch代码块。如果产生异常,执行catch代码块,程序继续执行。
public class Test01 { public static void main(String[] args) { Scanner sc = new Scanner(System.in); System.out.println("请输入被除数:"); int num1, num2; try { num1 = sc.nextInt(); System.out.println("请输入除数:"); num2 = sc.nextInt(); int r = num1 / num2; System.out.println("r = " + r); } catch (Exception e) { System.out.println("oops,产生异常:"+e); } System.out.println("程序结束..."); } }
Exception 类是所有异常类的父类。
如果程序在try中产生异常,进入catch前需要类型匹配,所以写多个catch代码块。
try { num1 = sc.nextInt(); System.out.println("请输入除数:"); num2 = sc.nextInt(); int r = num1 / num2; System.out.println("r = " + r); } catch (InputMismatchException e) { System.out.println("产生输入不匹配异常:"+e); } catch (ArithmeticException e) { System.out.println("产生数学计算异常:"+e); } catch(Exception e) { System.out.println("通用异常:"+e); }
通常需要一个Exception用于匹配所有异常,一般放到最后一个catch块中。如果前方有匹配异常,Exception不在匹配。
Excepiotn 常用方法
[1] toString() 输出异常的类型和描述信息
e.g: java.lang.ArithmeticException: / by zero
[2] getMessage() 返回异常的描述信息
[3] printStackTrace() 打印执行堆栈
java.util.InputMismatchException at java.util.Scanner.throwFor(Scanner.java:864) at java.util.Scanner.next(Scanner.java:1485) at java.util.Scanner.nextInt(Scanner.java:2117) at java.util.Scanner.nextInt(Scanner.java:2076) at cn.exception02.Test02.main(Test02.java:14)
第一行:toString()的返回值
最后一行:出现异常的方法和出现异常的具体行数
注意:e.printStackTrace() 在控制中输出的位置不是按照程序流程输出的。
System.err.println() 在控制台中以红色字体提示错误信息。
try-catch-finally
[1]try-catch和之前一样用法,finally代码块表示不管是否出现异常,都执行的代码块。
public class Test01 { public static void main(String[] args) { Scanner sc = new Scanner(System.in); try { sc.nextInt(); } catch (Exception e) { e.printStackTrace(); } finally { System.out.println("finally 代码块"); } System.out.println("程序结束"); } }
finally 代码块中一般用于放置一些关闭数据库、关闭文件、关闭网络连接等释放资源的代码。
try { // 打开数据库 // 增删改查 } catch (Exception e) { // 异常处理 } finally { // 关闭数据库 }
[2] 存在return try-catch-finally代码块的执行顺序
public static int add() { System.out.println("请输入第一个操作数:"); Scanner sc = new Scanner(System.in); int num1, num2; int r = 0; try { num1 = sc.nextInt(); System.out.println("请输入第二个操作数:"); num2 = sc.nextInt(); r = num1 + num2; return r; } catch (Exception e) { System.out.println("出现异常."); return r; } finally { System.out.println("finally"); sc.close(); } }
[3]jvm 退出时finally代码块不在执行。
public class Test02 { public static void main(String[] args) { Scanner sc = new Scanner(System.in); try { sc.nextInt(); System.exit(0); } catch (Exception e) { e.printStackTrace(); } finally { System.out.println("finally 代码块"); } System.out.println("程序结束"); } }
多重catch块
多重catch可以用到try-catch中,也可以用到try-catch-finally中。
原则:
[1] 先子类后父类
[2] 发生异常时按顺序逐个匹配
[3] 只执行第一个与异常类型匹配的catch语句
异常的分类
Throwable 类是 Java 语言中所有错误(Error)或异常(Exception)的父类,只有当对象是此类(或其子类之一)的实例时,才能通过 Java 虚拟机或者 Java throw 语句抛出。
Error 类表示错误类。仅靠程序本身无法恢复的严重错误。jvm内存耗尽、jvm崩溃等。
Exception 类表示异常类,可以通过java 异常处理机制处理。
Exception 更加是否处理分为两种情况。
RuntimeException:运行时异常。不要求程序必须做出处理。是所有运行时异常的父类。
CheckedException:检查时异常。要求程序必须处理,不处理编译不通过。
import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Scanner; public class Test01 { public static void main(String[] args) { // 运行时异常 /*Scanner sc = new Scanner(System.in); System.out.println("请输入1-3之间的数字:"); int num = sc.nextInt(); System.out.println(num);*/ // 检查时异常 try { SimpleDateFormat df = new SimpleDateFormat(); df.parse("123"); } catch (ParseException e) { e.printStackTrace(); } } }
常见的运行时异常
ArithmeticException:数学计算异常。比如除数为0
ArrayIndexOutofBoundsException:数组下标越界异常。
NullPointException:空指针异常
IllegalArgumentException:非法参数异常。
ClassCastException:强制类型转换异常。
NumberFormatException:数字格式化异常。比如把“abc”格式化成数字。
常见的检查时异常:
ClassNotFoundException:类没有被发现异常。
SQLException:数据库先关异常
IOException:IO操作异常
ParseException:解析错误异常
FileNotFoundException:文件未发现异常。
运行时异常和检查时异常的区别
运行时异常:包括RuntimeaException及其所有子类。不要求程序必须对它们作出处理,比如InputMismatchException、ArithmeticException、NullPointerException等。即使没有使用try-catch或throws进行处理,仍旧可以进行编译和运行。如果运行时发生异常,会输出异常的堆栈信息并中止程序执行。
Checked异常(非运行时异常):除了运行时异常外的其他异常类都是Checked异常。程序必须捕获或者声明抛出这种异常,否则出现编译错误,无法通过编译。处理方式包括两种:通过try-catch捕获异常,通过throws声明抛出异常从而交给上一级调用方法处理
声明异常
声明异常的概念
当一个方法可能存在异常时,如果不想在方法内处理(不合适在方法内处理、想让调用者处理)时,可以选择在方法上声明异常。形式:
public 返回值 方法名(形参) throws 异常类1,异常类2,…{ }
public static int div(int a,int b) throws ArithmeticException{ int r = a / b; return r; }
所谓声明异常,就是当调用者调用该方式时知道该方法可能产生异常,并处理异常。
public class Test01 { public static int div(int a) throws ArithmeticException,InputMismatchException{ System.out.println("请输入第二个参数:"); Scanner sc = new Scanner(System.in); int b = sc.nextInt(); int r = a / b; return r; } public static void main(String[] args) { // 处理div声明的异常 try { Test01.div(10); } catch (InputMismatchException | ArithmeticException e) { e.printStackTrace(); } } }
如果一个方法抛出异常必须被外界(调用处)处理时,可以声明检查时异常。否则,声明运行时异常。
public class Test02 { // 声明检查时异常 public static int div(int a) throws Exception{ System.out.println("请输入第二个参数:"); Scanner sc = new Scanner(System.in); int b = sc.nextInt(); int r = a / b; return r; } public static void main(String[] args) { try { Test02.div(10); } catch (Exception e) { e.printStackTrace(); } } }
如果在方法调用处也不知道如何处理时,可以选择继续向上抛出异常。
直到在main方法上声明异常。此时如果main方法执行时产生异常,jvm负责处理。
import java.util.InputMismatchException; import java.util.Scanner; public class Test03 { // 声明检查时异常 public static int div(int a) throws Exception{ System.out.println("请输入第二个参数:"); Scanner sc = new Scanner(System.in); int b = sc.nextInt(); int r = a / b; return r; } public static void main(String[] args) throws Exception{ // 调用处也不知道如何处理,继续上抛异常 => 在main上声明异常。 Test03.div(10); } }
总结:
声明异常实际上就是一个异常上抛的过程。
重载的关系
声明异常和重载没有任何关系。
重写的关系
声明一个和重写有关系
[1] 如果父类声明了运行时异常,子类可以不声明任何异常或者继承声明运行时异常。
package cn.exception07; public class Father { public void showInfo() throws RuntimeException{ System.out.println("father:showInfo"); } }
package cn.exception07; public class Son extends Father{ @Override public void showInfo() { super.showInfo(); System.out.println("我是son:showInfo"); } }
[2] 如果父类没有声明异常,子类不用声明异常或者可以声明运行时异常。
public class Father { public void showInfo() { System.out.println("father:showInfo"); } }
@Override public void showInfo() throws RuntimeException{ super.showInfo(); System.out.println("我是son:showInfo"); }
[3] 如果父类声明了Exception,子类要么在内部处理掉,要么继续向上抛。
public class Father { public void showInfo() throws Exception{ System.out.println("father:showInfo"); } }
public class Son extends Father{ @Override public void showInfo() { try { super.showInfo(); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println("我是son:showInfo"); } }
public class Son extends Father{ @Override public void showInfo() throws Exception { super.showInfo(); System.out.println("我是son:showInfo"); } }
手动抛出异常
除了jdk自身抛出的异常,开发者可以根据业务需要手动抛出异常。使用关键字throw
public void setAge(int age) throws Exception{ if(age > 0) { this.age = age; }else { throw new Exception("年龄不合法"); } }
如果要抛出必须处理的异常,throw new Exception;
如果要抛出可以处理可不处理的异常,throw new RuntimeException
自定义异常
很多时候,jdk中定义的异常根本不能满足具有业务的需要。可以考虑自定义异常。
步骤:
[1] 继承异常类(Exception/RuntimeException)
[2] 定义构造方法。一般需要调用父类的构造方法。
public class AgeException extends Exception{ public AgeException() { super(); } public AgeException(String message) { super(message); } public int showInfo() { System.out.println(this.getClass()+"=>"+super.getMessage()); return 0; } }