-
异常的概述
-
异常就是Java程序在运行过程中出现的错误。
-
-
异常的分类
-
Throwable Throwable类是Java语言中所有错误(Error)或异常(Exception)的超类。只有当对象时此类(或其子类之一)的实例时,才能通过Java虚拟机或者Java throw语句抛出。
-
Error Throwable的子类。服务器宕机,数据库崩溃等
-
Exception Throwable的子类。 异常的继承体系。
-
RuntimeException
-
Exception的子类。
- 运行时异常,是程序员常犯的错误。
-
-
-
-
2. JVM默认是如何处理异常的
-
JVM默认是如何处理异常的
-
main函数收到这个问题时,有两种处理方式:
-
自己将该问题处理,然后继续运行
-
自己没有针对的处理方式,只有交给调用main的jvm来处理
-
-
jvm有一个默认的异常处理机制,就将该异常进行处理。
-
并将该异常的名称,异常的信息,异常出现的位置打印在了控制台上,同时将程序停止运行。
-
-
-
案例演示
-
JVM默认如何处理异常
-
package com.heima.exception; public class Demo1_Exception { /** * @param args */ public static void main(String[] args) { // demo1(); Demo d = new Demo(); int x = d.div(10, 2); System.out.println(x); int y = d.div(10,0); // ArithmeticException 当出现异常的运算条件时,抛出此异常。 System.out.println(y); /* * java.lang.Object java.lang.Throwable java.lang.Exception java.lang.RuntimeException java.lang.ArithmeticException */ } public static void demo1() { int[] arr = {11,22,33,44,55}; System.out.println(arr[7]); // ArrayIndexOutOfBoundsException索引越界异常 arr = null; System.out.println(arr); System.out.println(arr[0]); // NullPointerException } } class Demo { // 除法 public static int div(int a, int b) { return a/b; } }
默认抛出一个异常对象,如上述示例中的ArithmeticException对象,new ArithmeticException("/ by zero")
-
Exception in thread "main" 5 java.lang.ArithmeticException: / by zero at com.heima.exception.Demo.div(Demo1_Exception.java:41) at com.heima.exception.Demo1_Exception.main(Demo1_Exception.java:14)
-
3. try...catch的方式处理异常1
-
异常处理的两种方式
-
try…catch…finally
-
try catch
-
try catch finally
-
try finally
-
-
throws
-
-
try...catch处理异常的基本格式
-
try…catch…finally
-
-
案例演示
-
try...catch的方式处理1个异常
-
package com.heima.exception; public class Demo2_Exception { /** 异常处理的两种方式 try…catch…finally try catch try catch finally try finally throws try...catch处理异常的基本格式 try…catch…finally 案例演示 try...catch的方式处理1个异常 try:用来检测异常 catch:用来补获异常 finally:是用来释放资源的 */ public static void main(String[] args) { Demo2 d = new Demo2(); int x = d.div(10, 2); System.out.println(x); try { int y = d.div(10,0); System.out.println(y); } catch (ArithmeticException a) { // ArithmeticException a = new ArithmeticException(); System.out.println("除数不能是0"); } } } class Demo2 { // 除法 public static int div(int a, int b) { return a/b; } }
-
4. try...catch的方式处理异常2
-
案例演示
-
try...catch的方式处理多个异常
-
package com.heima.exception; public class Demo3_Exception { /** try...catch的方式处理多个异常 JDK7以后处理多个异常的方式及注意事项 安卓:客户端开发,如何处理异常?try {} catch(Exception e) {} ee:服务端开发:一般都是底层开发,从底层向上抛,通过错误日志查找 try后面如果跟多个catch,那么小的异常放前面,大的异常放后面, 根据多态的原理,如果大的放前面,就会将所有的子类对象接收,后面的catch就没有意义了 */ public static void main(String[] args) { int a = 10; int b = 0; int[] arr = {11,22,33,44,55}; try { System.out.println(a/b); System.out.println(arr[10]); } catch (ArithmeticException e) { System.out.println("除数不能为0"); } catch (ArrayIndexOutOfBoundsException e) { System.out.println("索引越界了"); } catch (Exception e) { System.out.println(e); } } }
-
安卓:客户端开发,如何处理异常?try {} catch(Exception e) {}
- ee:服务端开发:一般都是底层开发,从底层向上抛,通过错误日志查找
- try后面如果跟多个catch,那么小的异常放前面,大的异常放后面,根据多态的原理,如果大的放前面,就会将所有的子类对象接收,后面的catch就没有意义了
-
-
JDK7以后处理多个异常的方式及注意事项
-
package com.heima.exception; public class Demo3_Exception { /* JDK7以后处理多个异常的方式及注意事项 */ public static void main(String[] args) { int a = 10; int b = 0; int[] arr = {11,22,33,44,55}; //JDK7以后处理多个异常的方式及注意事项 try { System.out.println(a/b); System.out.println(arr[10]); } catch (ArithmeticException | ArrayIndexOutOfBoundsException e ) { System.out.println("除数不能为0 或 索引越界异常"); } } }
-
-
5. 编译期异常和运行期异常的区别
-
编译期异常和运行期异常的区别
-
Java中的异常被分为两大类:编译时异常和运行时异常。
-
所有的RuntimeException类及其子类的实例被称为运行时异常,其他的异常就是编译时异常。
-
编译时异常
-
Java程序必须显示处理,否则程序就会发生错误,无法通过编译
-
-
运行时异常
-
无需显示处理,也可以和编译时异常一样处理
-
-
-
案例演示
-
编译期异常和运行期异常的区别
-
package com.heima.exception; import java.io.FileInputStream; public class Demo4_Exception { /** 编译期异常和运行期异常的区别 Java中的异常被分为两大类:编译时异常 和 运行时异常。 所有的RuntimeException类及其子类的实例被称为运行时异常,其他的异常就是编译时异常。 编译时异常 Java程序必须显示处理,否则程序就会发生错误,无法通过编译 运行时异常 RuntimeException 无需显示处理,也可以和编译时异常一样处理 案例演示 编译期异常和运行期异常的区别 编译时异常: 在编译某个程序时,有可能会有这样或那样的事情发生,比如文件找不到,这样的异常就必须在编译时进行处理。 如果不解决,则无法进行成功编译; */ public static void main(String[] args) { try { FileInputStream fis = new FileInputStream("xxx.txt"); } catch (Exception e) { System.out.println(e); // java.io.FileNotFoundException: xxx.txt (系统找不到指定的文件。) } } }
-
6. Throwable的几个常见方法
-
Throwable的几个常见方法
-
getMessage()
-
获取异常信息,返回字符串。
-
-
toString()
-
获取异常类名和异常信息,返回字符串。
-
-
printStackTrace()
-
获取异常类名和异常信息,以及异常出现在程序中的位置。返回值void。
-
-
-
案例演示
-
Throwable的几个常见方法的基本使用
-
package com.heima.exception; public class Demo5_Throwable { /** Throwable的几个常见方法 getMessage() 获取异常信息,返回字符串。 toString() 获取异常类名和异常信息,返回字符串。 printStackTrace() 获取异常类名和异常信息,以及异常出现在程序中的位置。返回值void。 案例演示: Throwable的几个常见方法的基本使用 */ public static void main(String[] args) { try { System.out.println(1/0); } catch (Exception e) { // Exception e = new ArithmeticException("/ by zero"); System.out.println(e.getMessage()); // 获取异常信息 System.out.println(e.toString()); // 获取异常类名和异常信息 System.out.println(e); // 默认使用toString方法,获取异常类名和异常信息 e.printStackTrace(); // 获取异常类名和异常信息,以及异常出现在程序中的位置。返回值void。 } } }
-
7. throws的方式处理异常
-
throws的方式处理异常
-
定义功能方法时,需要把出现的问题暴露出来让调用者去处理。
-
那么就通过throws在方法上标识。
-
-
案例演示
-
举例分别演示编译时异常和运行时异常的抛出
- 编译时异常的抛出,必须对其进行处理
- 运行时异常的抛出,可以处理,也可以不处理
-
package com.heima.exception; public class Demo6_Exception { /** throws的方式处理异常 定义功能方法时,需要把出现的问题暴露出来让调用者去处理。 那么就通过throws在方法上标识。 */ public static void main(String[] args) throws Exception { Person p = new Person(); p.setAge(-17); System.out.println(p.getAge()); } } class Person { private String name; private int age; public Person() { super(); } public Person(String name, int age) { super(); this.name = name; this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) throws Exception{ if(age > 0 && age <=150) { this.age = age; } else { throw new Exception("年龄非法"); } } }
-
8. throw的概述以及和throws的区别
-
throw的概述
-
在功能方法内部出现某种情况,程序不能继续运行,需要进行跳转时,就用throw把异常对象抛出。
-
-
案例演示
-
分别演示编译时异常对象和运行时异常对象的抛出
-
-
throws和throw的区别
-
throws
-
用在方法声明后面,跟的是异常类名
-
可以跟多个异常类名,用逗号隔开
-
表示抛出异常,由该方法的调用者来处理
-
-
throw
-
用在方法体内,跟的是异常对象名
-
只能抛出一个异常对象名
-
表示抛出异常,由方法体内的语句处理
-
-
9. finally关键字的特点及作用
-
finally的特点
-
被finally控制的语句体一定会执行
-
特殊情况:在执行到finally之前jvm退出了(比如System.exit(0))
-
-
finally的作用
-
用于释放资源,在IO流操作和数据库操作中会见到
-
-
案例演示
-
finally关键字的特点及作用
-
package com.heima.exception; public class Demo7_Finally { public static void main(String[] args) { try { System.out.println(1/0); } catch (Exception e) { System.out.println("除数为0,请修改"); return; } finally { System.out.println("执行finally"); } } }
- return语句相当于是方法的最后一口气,那么在它将死之前会看一看有没有finally,帮其完成遗愿。
- 如果有就将finally执行后,彻底返回。
-
-
10. finally关键字的面试题
-
面试题1
-
final,finally 和 finalize的区别
- final 可以修改类,不能被继承
- final 可以修饰方法,不能被重写
- final 可以修饰变量,只能赋值一次;
-
- finally 是try语句中的一个语句体,不能单独使用,用来释放资源;
- finalize 是Object类中的一个方法。当垃圾回收器不存在对该对象的更多引用时,由对象的垃圾回收器调用此方法。子类重写finalize方法,以配置系统资源或执行其他清除。
-
-
面试题2
-
如果catch里面有return语句,请问finally的代码还会执行吗?如果会,请问是在return前还是return后。
- 会执行,在return前。
-
package com.heima.test; public class Test1 { /** * @param args */ public static void main(String[] args) { Demo d = new Demo(); System.out.println(d.method()); /*输出: 回头看我一眼 30 */ } } class Demo { public static int method() { int x = 10; try { x = 20; System.out.println(1/0); return x; } catch (Exception e) { x = 30; return x; } finally { x = 40; System.out.println("回头看我一眼"); // 千万不要在finally里面写 return语句, // 因为finally的作用是为了释放资源,是肯定会执行的, // 如果在这里面写返回语句,那么try 和 catch 的结果都会被改变。这样写是犯罪啊 } } }
- 在catch语句的return x; 时,执行,建立好了一个“return x;”的返回路径,这时的x是30;然后再去找到finally,执行完x=40;赋值,不会影响到已经建立好的 “return x;”的返回路径中的x的值。
-
11. 自定义异常概述和基本使用
-
为什么需要自定义异常
- 通过名字区分到底是什么异常,有针对的解决方法,方便排错
-
举例:人的年龄
-
package com.heima.exception; public class Demo8_Exception { /** 自定义异常 自定义异常概述 继承自Exception 继承自RuntimeException */ public static void main(String[] args) { } } class AgeOutOfBoundsException extends Exception { public AgeOutOfBoundsException() { super(); } public AgeOutOfBoundsException(String message) { super(message); } }
-
package com.heima.exception; public class Demo6_Exception { /** throws的方式处理异常 定义功能方法时,需要把出现的问题暴露出来让调用者去处理。 那么就通过throws在方法上标识。 */ public static void main(String[] args) throws Exception { Person p = new Person(); p.setAge(-17); System.out.println(p.getAge()); } } class Person { private String name; private int age; public Person() { super(); } public Person(String name, int age) { super(); this.name = name; this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) throws AgeOutOfBoundsException{ if(age > 0 && age <=150) { this.age = age; } else { throw new AgeOutOfBoundsException("年龄非法"); } } }
-
自定义异常概述
-
继承自Exception
-
继承自RuntimeException
-
-
案例演示
-
自定义异常的基本使用
-
12. 异常的注意事项及如何使用异常处理
-
异常注意事项
-
子类重写父类方法时,子类的方法必须抛出相同的异常或父类异常的子类。(父亲坏了,儿子不能比父亲更坏)
-
如果父类抛出了多个异常,子类重写父类时,只能抛出相同的异常或者是他的子集,子类不能抛出父类没有的异常
-
如果被重写的方法没有异常抛出,那么子类的方法绝对不可以抛出异常,如果子类方法内有异常发生,那么子类只能try,不能throws
-
-
如何使用异常处理
-
原则:如果该功能内部可以将问题处理,用try,如果处理不了,交由调用者处理,这是用throws
-
区别:
-
后续程序需要继续运行就try
-
后续程序不需要继续运行就throws
-
-
如果JDK没有提供对应的异常,需要自定义异常。
-
13. 练习
-
键盘录入一个int类型的整数,对其求二进制表现形式
-
如果录入的整数过大,给予提示,录入的整数过大请重新录入一个整数BigInteger
-
如果录入的是小数,给予提示,录入的是小数,请重新录入一个整数
-
如果录入的是其他字符,给予提示,录入的是非法字符,请重新录入一个整数
-
-
package com.heima.test; import java.math.BigDecimal; import java.math.BigInteger; import java.util.Scanner; public class Test2 { /* * 键盘录入一个int类型的整数,对其求二进制表现形式 如果录入的整数过大,给予提示,录入的整数过大请重新录入一个整数BigInteger ==> 如果录入的是小数,给予提示,录入的是小数,请重新录入一个整数 ==> NumberFormatException 如果录入的是其他字符,给予提示,录入的是非法字符,请重新录入一个整数 ==> NumberFormatException */ public static void main(String[] args) { Scanner sc = new Scanner(System.in); System.out.println("请输入一个整数:"); Boolean flag = false; while(!flag) { String s = sc.nextLine(); try { int i = Integer.parseInt(s); // 将字符串转换为整数 System.out.println(Integer.toBinaryString(i)); flag = true; } catch(NumberFormatException e1) { try { new BigInteger(s); System.out.println("您输入的数字过大,请重新输入:"); } catch (NumberFormatException e2) { try { new BigDecimal(s); System.out.println("您输入的数字是小数,请重新输入:"); } catch (NumberFormatException e3) { System.out.println("您输入的数字不是数字,请重新输入:"); } } } } // try { // int x = sc.nextInt(); // } catch (Exception e) { // System.out.println("录入的整数过大请重新录入一个整数"); // } catch (Exception e) { // System.out.println("录入的整数过大请重新录入一个整数"); // } catch (Exception e) { // System.out.println("录入的整数过大请重新录入一个整数"); // } } }
- 补充:不应该使用try-catch作为条件判断的方式,应该是对输入的值进行格式检查和判断。
14. 异常处理的注意事项,开发规则
1. 【强制】 Java类库中定义的可以通过预检查方式规避的 RuntimeException 异常不应该通过 catch 的方式来处理,比如:NullPointerException,IndexOutOfBoundsException等等。说明:无法通过预检查的异常除外,比如:在解析字符串形式的数字时,不得不通过catch NumberFormatException 来实现。
- 正例:if(obj != null) {...}
- 反例:try { obj.method(); } catch (NullPointerException e ) {...}
2. 【强制】异常不要用来做流程控制,条件控制。
说明:异常设计的初衷是解决程序运行中的各种意外情况,且异常的处理效率比条件判断方式要低很多
3. 【强制】catch时请分清稳定代码和非稳定代码,稳定代码指的是无论如何不会出错的代码。对于非稳定代码的catch尽可能进行区分异常类型,再做对应的异常处理。
说明:对大段代码进行 try-catch,使程序无法根据不同的异常做出正确的应激反应,也不利于定位问题,这是一种不负责任的表现。
- 正例: 用户注册的场景中,如果用户输入非法字符,或用户名称已经存在,或用户输入密码过于简单,在程序上作出分门别类的判断,并提示个用户。
4. 【强制】捕获异常是为了处理它,不要捕获了却什么都不处理而抛弃之,如果不想处理它,请将该异常抛给它的调用者。最外层的业务使用者,必须处理异常,将其转化为用户可以理解的内容。
5. 【强制】有try块放到了事务代码中,catch异常后,如果需要回滚事务,一定要注意手动回滚事务。
6. 【强制】finally块 必须对资源对象、流对象进行关闭,有异常也要做try-catch。
说明:如果JDK7及以上,可以使用try-with-resources方式。
7. 【强制】不要在finally块中使用return。
说明:finally块中的return返回后方法结束执行,不会再执行try块中的return语句。
8. 【强制】捕获异常与抛异常,必须是完全匹配,或者捕获异常是抛异常的父类。
说明:如果预期堆放抛的是绣球,实际接到的是铅球,就会产生意外情况。
9. 【推荐】方法的返回值可以为null,不强制返回空集合,或者空对象等,必须添加注释充分说明什么情况会返回null值。
说明:明确防止NPE(NullPointerException)是调用者的责任。即使被调用方法返回空集合或者空对象,对调用者来说,也并非高枕无忧,必须考虑到远程调用失败、序列化失败、运行时异常等场景返回null的情况。
10. 【推荐】防止NPE,是程序员的基本修养,注意NPE产生的场景:
- 返回类型为基本数据类型,return包装数据类型的对象时,自动拆箱有可能产生NPE。
- 反例: public int f() { return Integer 对象},如果为null,自动解箱抛出NPE。
- 数据库的查询结果可能为null。
- 集合里的元素即使 isNotEmpty,取出的数据元素也可能为null。
- 远程调用返回对象时,一律要求进行空指针判断,防止NPE。
- 对于Session中获取的数据,建议NPE检查,避免空指针。
- 级联调用 obj.getA().getB().getC();一连串调用,易产生NPE。
- 正例:使用JDK8的Optional类来防止NPE问题。
11. 【推荐】定义时区分 unchecked/ checked 异常,避免直接抛出 new RuntimeException(),更不允许抛出Exception或者Throwable,应使用有业务含义的自定义异常。推荐业界已定义过的自定义异常,如:DAOException / ServiceException等。
12. 【参考】对于公司外的 http/api 开放接口必须使用“错误码”;而应用内部推荐异常抛出;跨应用间RPC调用优先考虑使用Result方式,封装isSuccess()方法、“错误码”、“错误简短信息”。
说明:关于RPC方法返回方式使用Result方式的理由:
- 使用抛异常返回方式,调用方如果没有捕获到就会产生运行时错误
- 如果不加栈信息,只是new 自定义异常,加入自己的理解的error message,对于调用端解决问题的帮助不会太多。如果加了栈信息,在频繁调用出错的情况下,数据序列化和传输的性能损耗也是问题。
13. 【参考】避免出现重复的代码(Don't Repeat Yourself),即DRY原则。
说明:随意复制和粘贴代码,必须会导致代码的重复,在以后需要修改时,需要修改所有的副本,容易遗漏。必要时抽取共性方法,或者抽象公共类,甚至是组件化。
正例:一个类中有多个public方法,都需要进行数行相同的参数校验操作,这个时候请抽取:private boolean checkParam(DTO dto) {...}