异常处理
异常的概述
什么是异常?
异常 不等于 报错,异常不是语法错误,虽然它在编译期或运行期都有可能出现,但异常是一种程序在执行过程中遇到的问题。而语法错误是程序在编译过程中遇到的问题
其次,它是Java预先定义好了的一系列可能出现的问题,然后给我们进行报告。Java把这些问题全部设计成了类,遇到问题以后,JVM就会用对应的异常类产生对象,交给我们进行处理。
//运行时异常 :语法正确 运行的时候中断程序,控制台打印异常信息 // 比如100/0 //编译期异常:当我们调用某个方法的时候,语法正确,但仍然报错,指向报错信息,出现"Unhandled exception: xxxxx" //举例:Unhandled exception: java.io.FileNotFoundException FileInputStream f = new FileInputStream("hello.txt");
学会看运行时异常的错误信息
Exception in thread "main" java.lang.ArithmeticException: / by zero at com.funfun.error.TestMain.main(TestMain.java:8) Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 9 out of bounds for length 3 at com.funfun.error.TestMain.main(TestMain.java:10)
1.Exception in thread "main" :异常发生在main线程当中的。
2.在1的后面,马上跟上的是异常类的类名。这些类名全部是见名知意的。
例如:
NullPointerException 空指针异常
ArrayIndexOutOfBoundsException 数组下标越界异常
3.在2的下面,可以看到
at.......(某.java:行);
有时候会看到同时出现很多行at,但我们找的位置是应该从上往下找第一个你写的文件的位置,就是异常发生的真正位置
异常类型
JDk预先定义好了的一系列异常类,这些异常类通过继承形成了一个树形结构
Throwable类 -- 所有异常类的根类。它下面有两个子类:Exception 和 Error (异常和错误)
在Java的概念中,异常不是错误。这两个类所描述的问题是不一样的。
Exception:能够用代码解决的问题
Error:无法用代码解决的问题,往往是运行环境或硬件问题。
Exception:异常类的根类
在Exception以下,又提供了一系列的子类,所以Java在设计的时候已经是考虑的非常详尽了,把各种可能发生的问题,都用了一个异常类来表示。在这些子类中,又分为:
1.运行时异常
- RuntimeException及其子类
特点:在编译期只要语法通过就能运行,在运行期一旦发生问题又没有处理,就会中断程序
2.编译时异常
- 除RuntimeException以外的,全部都是编译时异常
特点:在编译期告诉我们这里可能发生异常,需要处理否则不能运行
//运行期异常:语法正确,运行的时候中断程序,控制台打印异常信息 在一个try块当中有可能发生多个异常,但是运行的时候只会发生一个。 //编译期异常:当我们调用某个方法的时候,语法正确,但仍然报错,指向报错信息,出现"未处理的异常:****异常" 例如:FileInputStream fio = new FileInputStream("hello.txt");
异常对象的传播
当程序运行起来后,若发生了某种异常,JVM终止程序,再把这个异常对象,拿到代码中看是否进行了处理,如果没有处理,它就会退出当前方法,带着异常对象返回到方法调用处,并记录下退出位置;如果方法调用处也没有处理这个异常,那么调用处的方法也被终止,带着异常对象再往上一级传播,如果传到main方法还是没有处理,那么只能结束main方法,把异常对象的信息打印在控制台。而main方法结束时,整个程序结束。这个过程就是异常的传播。
结束方法的方式不仅仅是前面学的return或执行到方法末尾,执行中产生了异常且没有进行处理也会结束方法,方法结束后程序都是回到方法调用处
所以,如果想让程序发生异常后不死,只需要在传递过程中的任意一个地方进行处理即可。
异常处理的方式
(第一种更好,第二种是异常已经发生后的补救措施)
1.最好的状态是不给程序异常的机会 比如给其在执行前加if判断 几乎所有的运行时异常 都能通过这种方式来解决
NullPointerException //对象为null不会发生异常,而是当null对象调用普通方法和属性的时候,会抛出 if(对象 != null){ 对象.行为(); }
2.当异常发生以后,通过捕获异常对象,来进行处理
复习---Java的特性
1.简单性:语法简单(相对于C C++)
2.面向对象:Java是第一门纯面向对象的编程语言,它所有的语法关键字都是根据面向对象的思想及特征进行设计的
3.跨平台:一次编译处处运行。JVM是Java中非常伟大的一个发明。要掌握Java的工作原理,包括编译器,类加载器,解释器....
4.健壮与安全(卤棒性):Java中没有指针,不允许程序员直接操作内存,从而降低了程序员犯错的可能;Java提供了异常处理机制,就算程序在运行中出现了异常,也能通过该机制挽救回来;Java提供了垃圾回收机制GC,可以帮助程序的运行环境进行更换的清理而不是依赖于程序员的习惯。
5.多线程的实现更简单(相对于C C++)
try-catch-finally语句
这是Java中专门提供的异常处理语句,也是Java特性中健壮性的体现。
/* 在一个try块中有可能发生多个异常,但是在运行但时候只会发生一个 */ try{ Scanner sc = new Scanner(System.in); System.out.println("输入一个整数:"); int inputNum = sc.nextInt(); int result = 100/inputNum; System.out.println("结果是:"+result); } //这里不是写一个异常类 而是声明一个异常变量 //用变量去指向抛出的异常对象 catch(InputMismatchException ime){ System.out.println("发生了输入类型不匹配异常"); } catch (ArithmeticException ae){ System.out.println("发生了/0的数学异常"); } // 可以用父类的引用指向子类的对象,抓住全部异常 // 用来捕获没有测试出来的异常,只能写到最后了(捕获子类的写前面,父类写最后) catch(Exception ex){ System.out.println("发生了异常"); // if(ex instanceof ArithmeticException){ // ...... // } }
#try
书写正常逻辑的代码,这段代码有可能发生异常,让代码在try中试着运行
#catch
捕获某种类型的异常,书写捕获后你想执行的内容
1.catch后面的圆括号书写的是声明一个异常变量,一旦try块发生异常产生异常对象,就会跳到catch块用圆括号里面的异常变量去匹配异常对象,如果匹配上,说明该catch块就是处理该异常对象的,就会打断异常的传播,进入catch块花括号的执行,括号里面可以为空。只要匹配上 就算处理了异常。程序流程回归正常,不会往上一级跳。
2.如果一个try块中可能发生多种异常,可以连续书写多个catch块。
也可以用动态绑定(用父类的引用指向子类的对象),书写一个catch块抓住全部异常。
可以用父类的引用指向子类的对象,抓住全部异常 catch(Exception ex){ System.out.println("发生了异常"); if(ex instanceof ArithmeticException){ ...... } }
两种方式都可,如果所有异常的处理方式都是一样的,可以写父类,如果需要对不同的异常有不同操作,建议写多个catch块
注意:当书写多个catch块的时候,如果这多个catch块捕获的异常有继承关系,必须把捕获子类异常的catch块写在前面,捕获父类异常的catch块写在后面。
catch块如果没有匹配上,程序就会照常报错
#finally
不管是否发生异常,都必须要执行的代码,就算遇到return语句也不能阻止
唯一能阻止它的代码:System.exit(0) 这句代码的效果是关闭JVM
需要写在finally里面的代码:往往都是资源的清理动作,管道/连接的关闭动作。比如 scan.close();
#面试题目与细节
1.try-catch-finally是不是处理异常的唯一手段?
不是,异常处理还要一种提前判断的手段,大部分运行时异常都应该使用提前判断。编译时异常才更多采用这种手段。
2.try-catch以后是不是就万事大吉了?
并不是,异常处理的关键并不是把异常捕获住就完了。如果只是让程序不报错,就在main方法里面写一个try-catch就完了。这明显是不合适的。更多的工作是需要我们在捕获了之后,能够让程序回到正确的业务执行顺序上,继续完成我们的工作。
常见用法:
while(true){ try{ break; } catch{ } };
3.try-catch-finally是否必须同时出现?
不是。try后面必须接catch 或者 finally之一,也可以都有;catch和finally前面必须要有try
4.final finally finalize的区别
final是关键字 用来修饰变量的时候,表示是一个常量;用来修饰方法的时候,这个方法不能被重写;用来修饰类的时候,这个类不能被继承。
finally也是关键字,用在异常处理的try-catch-finally机制当中。写在它中间的代码表示不管是否发生异常都必须要执行的代码,通常是资源的清理,管道的关闭等。
finalize不是关键字,它是一个方法名,它是Object类的方法,专门用来销毁对象的,由垃圾回收GC调用。JDK9之后该方法过时。
5.finally内的代码在return之前还是之后执行?
finally是在return之前被执行的。
严格来说,finally不是在return语句之前被执行的,而是在return这个流程跳转动作之前执行的,return是流程跳转前的准备工作(返回值已经准备好了)已经做完了,然后再执行finally,然后再完成流程跳转。
简记:finally不能改变return后面的返回值的。
6.思考题?num的值 答案21
public void methodA(){ int r = methodB(); System.out.println(r); } public int methodB(){ int num = 20; try{ num ++; return num; }finally{ num += 5; } }