Java异常处理机制

来源:流浪舟 https://index.maliaoblog.cn

与C++、python,Ruby一样,Java也有一个异常处理机制,当程序出现异常时,系统会自动生成一个Exception对象通知程序去处理。之所以这样设计目的在于使程序有更好的容错性和可读性,实现业务代码和错误处理代码的分离。比如:

try{
	//业务代码
}catch(ExceptionClass e){
	//处理代码
}catch(Excepton i){//括号内为形参
    //是不是有点像if-else语句
}

抛出:生成一个异常对象后,异常送给运行环境处理,这里并不是什么严格定义,理解就行。那另一个专业词捕获意思是把异常交给catch异常处理块,就是catch异常,如果找不到合适的catch块会结束程序。每个catch块会处理专门的异常,就像园括号里的。多个catch块针对不同的异常,但每次只有一个catch块被执行。同时需要注意的是,catch块是不能访问try块的局部变量,只在try块中有效。

异常类的继承和分类

分类:
异常分类
所以从上面的图来看,非正常情况分为两种,ExceptionError。Error一般是指虚拟机相关的问题,如:虚拟机错误,系统故障等无法用程序修复的,结果就是程序中断。其实还可以把异常分为两大体系:Checked检查异常,Runtime运行时异常,所有RuntimeException类及其子类实例成为运行时异常,其他异常为可检查异常(认为可修复处理的,必须显式处理否则无法通过编译)。

程序总是把Exception异常放到最后,如果放到前面,程序将直接进入该catch块,try块异常语句后面其他块不会有机会被执行。(所以异常对象是Exception及其子类的实例) 原则上应该先处理小异常,再处理大异常

Java7提供多捕获异常

从Java7开始catch块可以捕获多个异常,注意以下几点即可:

  1. 多种异常用|隔开 2.异常变量不能被重新赋值,即它们都是隐式的final修饰
catch(FileNotFoundExcepton e|ArithmeticException i){...}

访问异常信息,异常跟踪栈

异常对象一般有以下几个方法:

  • getMessage():返回异常的详细描述
  • printStackTrace():将异常跟踪栈信息到标准错误输出
  • printStackTrace(PrintStream s):将异常跟踪栈信息输出到指定流
  • getStackTrace():返回该异常的跟踪栈信息(注意是返回)

一系列方法被调用,形成了方法栈,发生异常时,异常向方法调用者传播,然后再传给其调用者...,跟踪栈记录程序异常发生点,执行停止位置,并标明类、方法、所在行。跟踪栈总是最内部的被调用方法上传,直到最外部操作起点,通常为main方法或Thread类的run方法。

finally关键字

在异常处理中,这里的finally不是必要的,那为啥要增添一个不必要的关键字呢?前面一篇提到过GC垃圾回收机制只会回收堆内存中的资源,不会回收其他资源(比如像网络连接、文件、数据库连接等)。为了保证一定能回收这些资源,机制提供了finally关键字去处理。也就是说,finally块的优先级是比较高的,即使trycatch强制方法返回,除非要强制退出JVM,如System.exit(1)

异常处理嵌套

嵌套是指异常处理的代码的嵌套,可以在trycatchfinally块中。

finally{
	if(file != null){
		file.close();
	}
}

Java7增强了try语句,允许后面紧跟圆括号,括号内可声明,初始化多个需关闭的资源,也就是说,不用finally块也能最后回收这些资源,这里是指不用显式的块,其实还是隐式的用它。前提是,这些资源类必须实现AutoCloseable或Closeable接口,即需要重写close方法。Java7几乎把资源类都改写了,改写后都实现了两个或一个的接口。

Closeable是AutoCloseable的子接口,可以被自动关闭的类要么实现Closeable接口,要么实现AutoCloseable接口,但都需要实现close方法。Closeable接口的close方法只能抛出IOException异常,所以实现时只能抛出其及子类;而AutoCloseable的close方法能抛出所有异常类。

try(BufferedReader br = new BufferedReader("Test.java");
	PrintStream ps = new PrintStream(new FileOutputStream("a.txt"));){
	System.out.println(br.readLine());
	ps.println("ok!");
	}

throws声明

当程序不知道如何处理这种异常时,会将异常抛出,交给上级处理。如果main方法不知道处理,同样throws抛出异常给JVM处理。JVM会打印异常跟踪栈信息,中止程序运行。用法就是只能在方法签名中使用,但是可以抛出多个异常类。

throws IndexOutBoundsException,IOException...//抛出异常类

throws抛出异常时有限制:子类方法抛出的异常要是父类抛出异常类型或其子类,且不能比父类抛出的异常还多。

相对来说,抛出Runtime异常是比较省事的。

throw

当程序出现错误,系统会自动抛出异常,Java程序也会抛出异常。自行抛出异常由throw语句实现,注意没有sthrow语句可以单独使用,像普通程序语句一样,重要的是:它抛出的不是异常类,而是实例对象throw自行抛出的异常同样会中止当前程序执行,然后跳到相应的catch块处理异常。同时为了更好的处理异常,让用户有一个完美的体验,throwcatch可以结合使用,程序对异常部分处理,然后抛给调用者处理。

public void test() throws MyException{
	try{
		...
	}catch(Exception e){
		throw new MyException("抛出给方法调用者");
	}
}

如果throw语句抛出的是checked异常,throw语句可以放在try块中捕获,也可以放在带throws声明的抛出方法中,把异常交给该方法的调用者处理;如果抛出的是Runtime异常,则无须放在try块,也无需放在带throws声明的方法中。但是真的要处理的话,程序可以try...catch捕获该异常,也可以也可以不用理会。

这里需要提一下throw语句的一个变化:在Java7以前,编译器根据throw语句抛出的异常类型从而判定该方法需要声明throws抛出的异常类型,而Java7开始,增强了语句,编译器会根据实际判断throw语句处的异常类型,从而该方法可以声明抛出实际的异常类型。

public void test() throws FileNotFoundException{
	try{
		new FileOutputStream("a.txt");
	}catch(Exception e){
		e.printStackTrace();
		throw e;
	}
}

自定义异常

抛出异常时,选择合适的异常很有必要,所以需要自定义异常。自定义异常则需要继承Exception类,或继承RuntimeException异常。定义异常通常要两个构造器,一个无参,一个有参(字符串参数,描述异常详细信息,即getMessage方法返回值)

public class MyException extends Exception{
	public MyException(){} //需要的话可以换成RuntimeException
	public MyException(String msg){
		super(msg);//调用父类构造器
	}//msg会传给异常对象的message属性
}

异常链

在实际的开发中,原始的异常信息往往是不被外界用户所知晓的,但是异常发生时需要通知用户情况,因此在处理异常时会进行异常的链式处理

public test() throws MyException{
	try{
		//业务代码
	}catch(Exception e){
	//原始异常记录下来...
		throw new MyException("系统出现未知异常");//用户提示 
	}
}

这种捕获一个异常然后抛出一个异常,并把原始异常信息保存下来,被称为“异常链”,是一种典型的链式处理模式:职责链模式

JDK1.4开始,Throwable子类重载了一个构造器,该构造器可接收一个cause对象作为参数,cause表示原始异常,那这样就能把原始异常传给新异常,从而进行链式处理。

try{
//
}catch(SQLException e){
	//记录异常,再抛出异常对象
	throw new SQLException(e);
}catch(Exception e){
	throw new SQLException(e);
}

构造器很简单,如下:

public MyException(Throwable t){
	super(t);
}//已继承Exception类

异常处理规则

  1. 不要过度使用异常
  2. 不要使用过大的try
  3. 避免使用catchall 语句
  4. 不要忽略捕获的异常

异常处理的初衷是将业务代码和异常处理代码分离,所以别另作他用!此外,需要注意的是,异常处理的效率比正常的流程控制的差,所以异常处理使用过多也不是好事。catch All语句虽然可以处理所以异常,但需要分情况处理时得不偿失,或者需要得到特定异常时。


公众号: 菜鸡干Java
流浪舟 https://index.maliaoblog.cn

posted @ 2020-09-22 21:29  沧海一粟Z  阅读(140)  评论(0编辑  收藏  举报