Java入门7---异常处理
一、Java中异常的分类
当执行一个程序时,如果发现异常,则异常之后的代码就不再执行。Throwable 类是 Java 语言中所有错误或异常的超类,其子类有Exception和Error。JDK文档有这么一句话:只有当对象是此类(或其子类之一)的实例时,才能通过 Java 虚拟机或者 Java throw 语句抛出。类似地,只有此类或其子类之一才可以是 catch 子句中的参数类型。也就说Error也是catch子句的参数类型,不只是Exception。由于Error是错误,正常的程序时不应该捕获的,因为这些错误消除后可能再也不会出现,所以我们几乎从来没有看到catch子句中捕获Error的情况,但是Error还是可以被捕获。
1.Error
Java虚拟机无法解决的严重问题。如:JVM系统内部错误、资源耗尽等严重情况。一般不编写针对性的代码进行处理。
2.Exception
其它因编程错误或偶然的外在因素导致的一般性问题,可以使用针对性的代码进行处理。例如:
- 空指针访问
- 试图读取不存在的文件
- 网络连接中断
2.1 编译时异常
在编译期间会出现的异常(执行javac.exe命令时,出现异常)
2.2 运行时异常
在运行期间出现的异常(执行java.exe命令时,出现异常)
出现运行时异常后,系统会把异常一直往上层抛出,直到遇到处理代码为止。若没有处理块,则抛到最上层;如果是多线程就用Thread.run()方法抛出,如果是单线程,就用main()方法抛出。抛出之后,如果是线程,那么这个线程也就退出了。如果是主程序抛出的异常,那么整个程序也就退出了。所以,如果不对异常进行处理,后果是非常严重的,一旦发生,要么是线程中止,要么是主程序终止。
二、异常处理
- 对于运行时异常,可以不显示的进行处理
- 对于编译时异常,必须要显示的进行处理
1.如何处理Exception异常
- 上游:手动抛/自动抛
- 下游:try-catch-finally/throws
上下游各有两种方法,可以自由搭配。
抓抛模型:
- “抓”:抓住上一步抛出来的异常类的对象。如何抓?即为异常处理的方式。
- “抛”:一旦抛出此异常类的对象,那么程序就终止执行,此异常类的对象抛给方法的调用者。
1.1 抓
1. try-catch-finally
try{ // 可能出现异常的代码 }catch(Exception e1){ // 处理的方式1 }catch(Exception e2){ // 处理的方式2 }finally{ // 一定要执行的代码 }
注意:
- try内声明的变量,类似于局部变量,出了try{}语句,就不能被调用
- finally是可选的
- catch语句内部是对异常对象的处理
- 可以有多个catch语句,try中抛出的异常类对象从上往下去匹配catch中的异常类的类型,一旦满足就执行catch的代码。执行完,就跳出其后的多条catch语句。
- 如果异常处理了,那么其后的代码继续执行。
- 如果catch中多个异常类型是“并列”关系,谁上谁下都可以;如果catch中多个异常类型是“包含”关系,须将子类放在父类的上面,进行处理,否则报错!
- finally中存放的是一定会被执行的代码,不管try中、catch中是否仍有异常未被处理,以及是否有return语句。
- try-catch是可以嵌套的。
finally块中的return情况分析:
try语句在返回前,将其他所有的操作执行完,保留好要返回的值,而后转入执行finally中 的语句,而后分为以下三种情况:
情况一:如果finally中有return语句,则会将try中的return语句”覆盖“掉,直接执行finally 中的return语句,得到返回值,这样便无法得到try之前保留好的返回值。
情况二:如果finally中没有return语句,也没有改变要返回值,则执行完finally中的语句 后,会接着执行try中的return语句,返回之前保留的值。
情况三:如果finally中没有return语句,但是改变了要返回的值,分以下两种情况,
总体说明:将要返回变量重新赋予一个new的对象,返回还是之前的。
1)如果return的数据是基本数据类型或文本字符串,则在finally中对该基本数据的改变不 起作用,try中的return语句依然会返回进入finally块之前保留的值。
2)如果return的数据是引用数据类型,而在finally中对该引用数据类型的属性值的改变起 作用,try中的return语句返回的就是在finally中改变后的该属性的值
举例1:
没有异常 | 有异常 |
有finally时,先执行finally里面语句,再报异常。 |
举例2:面试题
2. 在方法的声明处显示的抛出该异常对象的类型 throws
如果一个方法(中的语句执行时)可能生成某种异常,但是并不能确定如何处理这种异常,则此方法应显示地声明抛出异常,表明该方法将不对这些异常进行处理,而由该方法的调用者负责处理。
如:
public void readFile(String File) thorws FileNotFoundException { ... // 读文件的操作可能产生FileNotFoundException类型的异常 FileInputStream fis = new FileInputStream(file); ...... }
当在此方法内部出现异常的时候,会抛出一个异常类的对象,抛给方法的调用者。
异常的对象可以逐层向上抛,直至main中。当然在向上抛的过程中,可以再通过try...catch...finally进行处理。
1.2 抛
1. 自动抛出 |
2. 手动抛出 |
|
|
抛异常,不处理(throw是回避问题的方式) |
|
抛异常,处理(try-catch是真正解决问题的方式)
|
手动抛出的异常类对象(throw new Exception("异常")),既可以是现成的异常类,也可以是自己创建的异常类。
|
3. 自定义异常类
方法:
- 继承现有的异常类;
- 提供一个序列号,提供几个重载的构造器。
举例1:
举例2:
举例3:
/** * 功能描述:自定义异常 * @since 2020-08-09 */ public class EcmDef { public static int ecm(int a, int b) throws EcmException{ // throws抓异常,捕获给调用该方法的地方,即EcmDef.ecm(num1,num2) if ( a < 0 || b < 0 ) { throw new EcmException("不能有负数"); // throw抛异常 } int res = a / b; return res; } public static void main(String[] args) { try { int num1 = Integer.parseInt(args[0]); int num2 = Integer.parseInt(args[1]); EcmDef.ecm(num1,num2); // 要处理异常 } catch (NumberFormatException e) { System.out.printf("数据类型不一致"); } catch (ArrayIndexOutOfBoundsException e) { System.out.printf("缺少命令行参数"); } catch (ArithmeticException e) { System.out.printf("被除数不能为0"); } catch (EcmException e) { // 处理throws抓到的异常 System.out.printf(e.getMessage()); } } } // 自定义异常类 class EcmException extends Exception { static final long serialVersionUID = -3386993124229549L; public EcmException() { } public EcmException(String msg) { super(msg); } }
|
4. 抛异常的方法的重写规则
子类重写父类的方法,其抛出的异常类型只能是被重写的方法的异常类的子类或异常类型一样。
因为如果子类抛出的异常比父类的还大,则在编译的时候不会报错,因为编译时候默认还是会catch父类抛出的异常,但是在方法的执行过程中,还是会执行子类的异常,如果抛出的异常大,catch方法会失效,说明程序的设计是有一定问题的。
三、try-with-resources
使用try-with-resources优雅的关闭资源。try-with-resources语句保证了每个声明了的资源在语句结束的时候都会被关闭。
任何实现了java.lang.AutoCloseable接口的对象,或者实现了java.io.Closeable接口的对象,都可以当做资源使用,Closeable继承了AutoCloseable,任何的catch和finally代码块都在所有被声明的资源被关闭后执行。