就这一次,搞定异常!

异常的分类

Throwable类

Throwable:所有异常都是由Throwable继承而来的,可以通过继承Throwable来实现新的异常,但是一般不推荐这样做,下一层分为了两个分支:ErrorException

Error类

Error类来用描述java运行时 系统内部引起的错误和资源消耗错误,因为是java内部的错误,因此编写的应用程序无能为力。

Exception类

Exception:Exception类又可以分为IOExceptionRuntimeException,一般程序应该通过检测的方式尽量避免RuntimeException(也就是说出现这种异常就是编写者自己的问题「编写者无需要捕获这一类异常」,相反,应该使用代码if(obj == null)「检测」避免出现NullPointException

RuntimeException类

RuntimeException:一般包括:

  • 错误的(类型)强制类型转换:ClassCastException

  • 数组访问越界:ArrayIndexOutOfBoundsException

  • 访问null指针:NullPointerException

IOException类

常见的IOException类一般包括:

文件末尾继续读取数据:EOFException

试图打开一个不存在的文件:FileNotFoundException

根据给定的字符串查找class对象,但是该类不存在:ClassNotFoundException

官方分类

Java一般把派生于Error类和RuntimeException类的异常称之为非检查型异常,而IOException称之为检查型异常

然而实际中,「我们能够进行处理的只有检查型异常,因为检查型异常在我们个人的控制之内,非检查型异常对于任何的代码都有可能抛出,出现这些异常的时候我们没法控制。」

举个例子:任何获取对象引用的方法,都有可能获取对象引用失败而返回一个NullPointerException,这异常的出现是没法控制的,除非你给每一个GetXX()方法都加上异常处理,但是这并不现实,因此没必要处理非检查型异常。

抛出异常

声明检查型异常

如何声明检查型异常

如下:

public FileInputStream(String name) throws FileNotFoundException

若要声明多个检查型异常,则需要用逗号分割

public Image loadImage(String name) throws FileNotFoundException, EOFException

注意这里特别强调声明检查型异常,对于抛出非检查型异常一般是不需要声明的

什么时候需要声明

两种情况:

  1. 调用一个抛出检查型异常的方法的时候,传递异常的抛出, 例如调用loadImage方法

  2. 方法本身需要抛出检查型异常


// situation 1
public Image loadNewImage(String name) throws FileNotFoundException, EOFException{
 loadImage(name);
}

// situation 2
public Image importFile(String name) throws FileNotFoundException{
 file f = readfile(name);
 if(f == null){
  throw new FileNotFoundException(); // 抛出异常
 }
}

抛出异常

抛出异常的方法:

// method 1:
throw new EOFException(); // 抛出一个EOFException

// method 2:
var a = new EOFException();
throw a;     // 抛出一个EOFException

一般怎样决定抛出异常呢?通常考虑的情况是这样:

  1. 找到一个合适的异常类

  2. 创建这一个异常类的对象

  3. 将对象抛出

一旦抛出异常之后,「这个方法不会返回到调用者,也就是说无需要另外建议一个返回语句(return)」

捕获异常

捕获异常

抛出检查型异常之后,如果没有对他进行捕获,当前执行的线程都会终止,那么如何捕获异常?

try{
 // 先执行try里面的语句
 // 一旦try里面的有一条语句抛出ExceptionTypeX类型异常,则进入相应的catch语句,终止之后的try代码块的执行
}catch(ExceptionType1 e){
 // 在这里处理异常
}catch(ExceptionType2 e){
 // 可以捕获多个异常
}catch(ExceptionType3 | ExceptionType4 e){
 // 如果抛出的两个异常是不同的,但是他们的处理方法都一样的话,还可以这样捕获
 // 注意这种方式捕获异常时,变量e被隐式声明为final,因此不能改变e的值
}
}finally{
 // 无论是否发生异常,最后都会运行此处的代码,通常用于释放资源
 // finally代码块可以省略
 // 注意不要把控制流的语句放在finally(return,throw,break,continue),因为会发生意想不到的错误
 // 同时也不应该过分依赖finally,一般的设计原则是将finally应用在关闭资源或者释放资源,如关闭IO流等
}

捕获异常还是传递异常

我们可以通过捕获异常来处理方法抛出的异常,但是并非每一个异常我们都知道怎么去处理,那要如何解决?

我们知道一个方法调用方法A,方法A会调用另外一个可能抛出异常的方法B时,我们可以通过在方法A中声明检查型异常的方式,来将方法B中的异常传递给调用了方法A的那一个方法(说得更加直白一点就是A方法将B方法抛给A的异常再次抛出给A的调用者),让它来解决这一个异常,因此对于上面的问题,我们就可以采用这种方式来解决,类似于下面的解决方式。

public A() throws Exception {
 B();
}
public B() throws Exception {
 ... // 处理代码
 if(...){
  throw new Exception();
 }
}

public fun(){
 try{
  A();
 }catch(Exception e){
   // do something
 }
}

实际上对于应该捕获异常还是传递异常这一个问题,最好的答案是 除非你知道怎么解决,否则就应该什么都不做,将异常传递给最终的调用者,让最终调用者来处理这个问题是最好不过的。

不过这种解决方案有一个例外:这个例外体现在继承特性上:

如果一个子类覆盖了超类的一个方法,那么子类要么不抛出异常,要么抛出的异常必须是超类异常的子类(也就是说子类需要抛出更加具体的异常),同样,如果覆盖的超类方法没有抛出异常,那么子类的覆盖方法也不能抛出异常。

因此,如果覆盖的超类方法没有抛出异常,而子类的覆盖方法又调用了其他可能抛出异常的方法,这种时候就必须在覆盖方法中捕获所有的异常。

自定义异常类

通常我们需要满足我们个人的一个程序需要的时候就需要自定义异常类,异常类的定义可以通过派生任何Exception类或者它的子类如IOException类来完成

public FileFormatException extends IOException
{
 public FileFormatException(){}
 public FileFormatException(String gride){
  super(gride);
 }
}
// 这样就可以完成自己的异常的定义了

自定义的异常类若是IOException(检查型异常),则最后的方法调用者必须捕获异常,编译器一般会给出下划线来提示调用者需要捕获该异常,而自定义的异常类若是RuntimeException(非检查型异常),则可以无需捕获,编译器也不会提示调用者,这也印证了开头所说的那句话:「你应该尽可能地避免非检查型异常!」

异常链与异常再次包装

异常嵌套

先看下面的代码:

InputStream i = ...;
try{
 ... // code 1
  try{
   // code 2
  }catch(Exception e){
  
  }finally{
   i.close(); 
  }
}catch(IOException e){

}

 

若finally中发生异常,则交由外层捕获处理,若code 2位置发生异常,交由内层处理。微信搜索公众号, [Java学习之道], 回复‘福利’ ,免费领取 2T 学习资料。

再次抛出异常

很多时候我们不知道需要具体抛出一个什么异常,或者抛出去的异常可能带有选择性等情况,这时候就需要再次抛出新的子类异常,类似于下面的情况

try{

}catch(IOException e){
 throw new FileNotFoundException();
}

包装技术

对于再次抛出异常,如何防止原父类异常中的信息丢失?

try{

}catch(IOException e){
 var a  = new FileNotFoundException();
 a.initCause(e);
 throw a;
}

 

捕获异常时,使用

 

Throwable original = a.getcause();

就可以获取高层原始异常信息了。

「包装技术非常有用,可以将多种异常包装成一类异常抛出,同时保留高层原始异常的信息。」

try-with-resource语句

该语句用于简化try-catch-finally语句中的释放工作

要使用try-with-resource语句,需要res实现AutoCloseable接口,该接口只有一个方法

void close() throws Exception
try(Resource res = ...){
 // work
}

使用try-with-resource,代码段在运行结束之后,无论是否有异常抛出,都会调用res中实现的close()

一般情况下,「只要需要关闭资源,就要尽可能使用try-with-resource

异常类相关方法

java.lang.Throwable

Throwable()          // 构造一个新的Throwable对象,但没有详细的描述信息
Throwable(String message)      // 构造一个新的Throwable对象,带有详细的描述信息
String getMessage()        // 获取Throwable对象的详细描述信息

// 通常用于包装的构造方法
Throwable(Throwable cause)      // 用给定的cause(原因)构造来构造Throwable对象
Throwable(String message, Throwable cause)  // 用给定的cause(原因)构造和描述信息来构造Throwable对象
Throwable initCause(Throwable cause)   // 为这个对象设置原因,如果对象已有原因则抛出异常,返回this
Throwable getCause()       // 获取这个对象所设置的原因,如果没有返回null

java.lang.Exception

Exception(Throwable cause)
Exception(String message, Throwable cause)  // 用给定的cause(原因)来构建Exception对象

 

猜你喜欢

转载自blog.csdn.net/Crystalqy/article/details/109288465