异常处理
1. 明确什么是异常 (重点)
2. 能辨识出常见的异常及其含义。 (熟悉+)
3. 理解异常产生的原理 (了解)
4. 能处理异常 (重点)
5. 能够自定义异常类型 (熟悉)
目的
我们发现异常,捕获异常的目的是要对异常进行补救,而不是打印一下。
什么是异常?
异常是在程序中导致程序中断运行的一种指令流。
例如,现在有如下的操作代码:
public class ExceptionDemo01{ public static void main(String argsp[]){ int i = 10 ; int j = 0 ; System.out.println("============= 计算开始 =============") ; int temp = i / j ; // 进行除法运算 System.out.println("temp = " + temp) ; System.out.println("============= 计算结束 =============") ; } }; 运行结果: ============= 计算开始 ============= Exception in thread "main" java.lang.ArithmeticException: / by zero at ExceptionDemo01.main(ExceptionDemo01.java:6)
以上的代码在“int temp = i / j ;”位置处产生了异常,一旦产生异常之后,异常之后的语句将不再执行了,所以现 在的程序并没有正确的执行完毕之后就退出了。
那么,为了保证程序出现异常之后仍然可以正确的执行完毕,所以要采用异常的处理机制。
处理异常
异常处理一般有两种方式,1.使用catch捕获异常; 2.使用throws关键字在方法声明时抛出异常
1.catch捕获异常
如果要想对异常进行处理,则必须采用标准的处理格式,处理格式语法如下:
try{
// 有可能发生异常的代码段
}catch(异常类型1 对象名1){
// 异常的处理操作 异常补救
}catch(异常类型2 对象名2){
// 异常的处理操作 异常补救
} ...
finally{
// 异常的统一出口
System.out.println("这里的代码无论是否出现异常都会执行,一般用于资源回收");
}
在进行异常的处理之后,在异常的处理格式中还有一个finally语句,那么此语句将作为异常的统一出口,不管是否产生 了异常,最终都要执行此段代码。
无论异常是否发生,finally必然执行。读取文件,占用文件,读取数据库等,都在finally中释放,因为finally必然执行。一般用于资源回收。
try+catch的处理流程
1、 一旦产生异常,则系统会自动产生一个异常类的实例化对象。
2、 那么,此时如果异常发生在try语句,则会自动找到匹配的catch语句执行,如果没有在try语句中,则会将异 常抛出.
3、 所有的catch根据方法的参数匹配异常类的实例化对象,如果匹配成功,则表示由此catch进行处理。
2.throws throw 抛出异常
在程序中异常的基本处理已经掌握了,但是随异常一起的还有一个称为throws关键字,此关键字主要在方法的声明上使 用,表示方法中不处理异常,而交给调用处处理。
1.throws是函数方法抛出异常,一般写在方法的头部,用来抛出一些异常,本身不进行解决,抛给方法的调用者进行解决(try catch)
格式: 返回值 方法名称()throws Exception{ }
Integer类: public static int parseInt(String text)throws NumberFormatException
2.throw是语句抛出异常,出现于函数内部,用来抛出一个具体异常实例,throw被执行后面的语句不起作用,直接转入异常处理阶段
一般是用户自定义的RuntimeException运行时异常,然后使用throw抛出。
1 public class Demo9 { 2 public static void main(String[] args) { 3 } 4 5 /** 6 * 异常是否抛出去, 应该站在哪个角度思考? 7 * 8 * 如果是因为传参导致异常 , 应该通过throws将异常抛出去. 9 * 10 * @param text 11 * @throws IOException : 因为传递的指令不对, 会导致此问题发生(s2=0时) 12 */ 13 public static void shutdown(String text) throws IOException { 14 Runtime.getRuntime().exec(text); 15 } 16 /** 17 * 此方法用于求两个参数的和 18 * 会将两个参数 转换为数字 求和 19 * @param s1 字符串参数1 20 * @param s2 字符串参数2 21 */ 22 public static void sum(String s1,String s2){ 23 int sum = Integer.parseInt(s1)+Integer.parseInt(s2); 24 System.out.println("和是:"+sum); 25 } 26 }
1 public class Person { 2 private String name; 3 private int age; 4 5 public String getName() { 6 return name; 7 } 8 9 public void setName(String name) { 10 this.name = name; 11 } 12 13 public int getAge() { 14 return age; 15 } 16 17 public void setAge(int age) { 18 if(age<0 || age>180){ 19 RuntimeException e = new RuntimeException("年龄不合理"); 20 throw e; 21 }else{ 22 this.age = age; 23 } 24 25 } 26 }
异常体系结构
异常指的是Exception , Exception类, 在Java中存在一个父类Throwable(可能的抛出)
Throwable存在两个子类:
1.Error:表示的是错误,是JVM发出的错误操作,只能尽量避免,无法用代码处理。
2.Exception:一般表示所有程序中的错误,所以一般在程序中将进行try…catch的处理。
RuntimeExcepion与Exception的区别
RuntimeException是Exception的子类,
catch(Exception e) 就是范围最大的捕获异常。如果为了方便,则可以将所有的异常都使用Exception进行捕获。
所有RuntimeException的子类即为非检查型异常;Exception的其余子类都为检查型异常。所谓“检查型异常”是指在源代码例必须显式地进行捕获处理,eclipse和idea都是自动编译的,当我们把代码写完的时候,如果有红线提示或者错误提示,那么就是检查异常。也就是说,当你看到某个方法声明中可能抛出某个检查型异常,那么作为调用方必须考虑如何处理这个异常,否则编译器就是给出错误提示。
所谓“非检查型异常”,也就是运行时异常,编译的时候不会给错误提示,运行的时候有可能出现异常。如: 用户输入0作为除数,就会 出现异常。通常是可以通过编码加以避免的逻辑错误,具体根据需要来判断是否需要捕获,并不会在编译期强制要求。例如NullPointerException、ArrayIndexOutOfBoundsException等。也就是说,程序员应该通过合理编码来努力避免程序出现这类异常,或者说程序出现这类异常就是程序员的责任。
异常处理常见面试题
1. try-catch-finally 中哪个部分可以省略?
答: catch和finally可以省略其中一个 , catch和finally不能同时省略 注意:格式上允许省略catch块, 但是发生异常时就不会捕获异常了,我们在开发中也不会这样去写代码.
2. try-catch-finally 中,如果 catch 中 return 了,finally 还会执行吗?
答:finally中的代码会执行 详解:
执行流程:
1. 先计算返回值, 并将返回值存储起来, 等待返回
2. 执行finally代码块
3. 将之前存储的返回值, 返回出去;
需注意:
1. 返回值是在finally运算之前就确定了,并且缓存了,不管finally对该值做任何的改变,返回的值都不 会改变
2. finally代码中不建议包含return,因为程序会在上述的流程中提前退出,也就是说返回的值不是try或 catch中的值
3. 如果在try或catch中停止了JVM,则finally不会执行.例如停电- -, 或通过如下代码退出 JVM:System.exit(0)。
try中有return时,finally也会执行,这个return值在finally执行之前的try中已经准备好了,复制备份放入了缓存。如果return返回的是基本数据类型,如int,那么finally中再给int赋值也不会改变return返回的值。如果return返回的是一个对象,那么复制到缓存中的是这个对象的地址,这时在finally中改变这个对象的值时,返回的值就是改变以后的值。
1 public class Demo7 { 2 public static void main(String[] args) { 3 int a = haha(); 4 System.out.println(a); 5 } 6 public static int haha(){ 7 int a = 10; 8 try{ 9 return a; 10 }catch(Exception e){ 11 12 }finally { 13 a = 20; 14 } 15 return 0; 16 } 17 static class Person{ 18 int age; 19 } 20 }
以上输出结果是:10
因为finally执行之前,retrun 的a是10,这个值已经复制到缓存中,输出时,就i时输出缓存中的值。finally再改变a的值也不会被输出。
1 public class Demo6 { 2 public static void main(String[] args) { 3 Person p = haha(); 4 System.out.println(p.age); 5 } 6 public static Person haha(){ 7 Person p = new Person(); 8 try{ 9 p.age = 18; 10 return p; 11 }catch(Exception e){ 12 return null; 13 }finally { 14 p.age = 28; 15 } 16 } 17 static class Person{ 18 int age; 19 } 20 }
以上输出结果是:28
try中return的p对象,放到输出缓存中是P对象的地址。finally中改变的是这个地址中的值,所以输出的值就是改变之后的值。