Java基础系列(三十一)——异常

本人花费半年的时间总结的《Java面试指南》已拿腾讯等大厂offer,已开源在github ,欢迎star!

本文GitHub https://github.com/OUYANGSIHAI/JavaInterview 已收录,这是我花了6个月总结的一线大厂Java面试总结,本人已拿大厂offer,欢迎star

原文链接:blog.ouyangsihai.cn >> Java基础系列(三十一)——异常

为什么有异常机制

假如在一个Java程序运行期间出现了一个错误,这个错误可能是由于文件包含了错误的信息,或者由于网络连接出现超时,或者就因为使用了无效的数组下标,或者试图使用一个没有赋值的对象(null)造成的。

当这些错误出现的时候,我们希望程序可以返回到一种安全的状态或者允许用户保存所有操作的结果,并且以妥善的方式终止程序。但是要做到这些事情,并没有我们想象中的那么简单,因为检测或者引发这个错误的代码通常离错误的源头较远。

这个时候就会用到我们的异常机制来去处理这些问题,它的原理是将控制权从错误产生的地方转移到能够处理这种情况的错误处理器。

Java中的异常

Java异常是Java提供的一种识别及响应错误的一致性机制。

Java异常机制可以使程序中异常处理代码和正常业务代码分离,保证程序代码更加优雅,并提高程序健壮性。

异常的分类

所有的异常都是由 Throwable继承来的,它是所有异常的父类,在下一层级被分为了 Error Exception

Error描述了Java运行时系统的内部错误或资源耗尽错误,如果出现了这种错误,我们能做到的只能是给通报给用户,然后尽力的去止损,其他我们并不能做到什么。这种情况很少出现。

Exception又被分为了 RuntimeException和非RuntimeException,区分的界限在于由程序错误导致的异常属于 RuntimeException;而程序本身没有问题,问题出在外部环境(比如 IOException)的这类异常属于其他异常。

举个栗子:

RuntimeException:

  1. 错误的类型转换( ClassCastException
  2. 数组访问越界( IndexOutOfBoundsException
  3. 空指针异常( NullPointerException

CheckedException:

  1. 输入、输出流异常( IOException
  2. 数据库操作异常( SQLException
  3. 用户自定义异常

这里有一条比较有用的准则:“如果出现了RuntimeException,那么一定是你的问题”。

Java语言规范将派生于 Error类或 RuntimeException类的所有异常称为非受查异常( unchecked)异常,所有其他的异常被称为受查( checked)异常,这里需要注意的是,Java的编译器会检查所有的受查异常是否提供了异常处理器。

抛出

在遇到异常的时候,抛出异常的这个方法不仅要告诉编译器返回值,还要告诉编译器有可能发生什么错误,但是在我们自己编写方法的时候,不必将所有可能抛出的异常都进行声明,至于什么时候需要在方法中用throws子句声明异常,什么异常必须使用throws子句进行声明,需要记住以下四种情况:

  1. 调用一个抛出受查异常的方法,例如:FileInputStream构造器。
  2. 程序运行过程中发现错误,并且利用throw子句抛出一个受查异常。
  3. 程序出现错误
  4. Java虚拟机和运行时库出现的内部错误。

在这四种情况当中,如果出现前两种情况时,必须要告诉调用这个方法的程序员有可能抛出异常,一个方法必须声明所有可能抛出的受查异常,而非受查异常要么不可控制,要么就应该避免发生,如果方法没有声明所有可能发生的受查异常,编译器就会发出一个错误信息,这段程序就无法通过编译。

Demo:

1234567
//1. 抛出一个public void download(String fileName) throws IOException{    ...} //2. 抛出多个public void read(String fileName, String targetFileName) throws IOException, EOFException {}

</code><code class="language-java" style="font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace; font-stretch: normal; line-height: inherit; background-color: transparent; border-radius: 3px; text-shadow: white 0px 1px; word-spacing: normal; word-wrap: normal; tab-size: 4; display: inline; border: 0px initial initial;">

那么,具体在什么时候抛出异常,如何进行抛出,如果需要对异常的位置有一个准确的判断后并抛出,可以使用以下的方法:

如果我们需要抛出的这个异常类是一个已经存在的异常类,我们只需要找到一个合适的异常类,创建这个类的一个对象,然后将这个对象抛出即可。

@SuppressWarnings(“ALL”)
class FileFormatExcetion extends IOException {


public FileFormatExcetion(){}

public FileFormatExcetion(String gripe){
    super(gripe);
}}

public void read(String fileName) throws FileFormatException{}

到目前为止,我们已经知道如何抛出一个异常,这个过程很容易,只要将这个异常抛出即可,但是,我们也不能一味的去抛出,如果一些运行时的错误完全可以在我们的控制之下,比如数组下标引发的错误,就应该将更多的时间花费在完善自己的代码上。

下面是一个捕获的简单的例子:

如果在try语句块中的任何代码抛出了一个在catch子句中说明的异常类,那么

当然,也有例外的情况,如果编写一个覆盖父类的方法,而这个方法又没有抛出异常,那么这个方法就必须捕获方法代码中出现的每一个受查异常,不允许在子类的throws说明符中出现超过父类方法所列出的异常类范围。

//这样
try{
//code

} catch (FileNotFoundException e) {
//handle1

} catch (UnknownHostException e) {
//handle2

} catch (IOException e) {
//handle3

}
//如果两个异常之前的处理动作一致可以是这样
//但是需要注意的是,这里的异常变量隐含为final变量,不能在子句中为e赋不同的值

try {
//code
} catch (FileNotFoundException | UnknownHostException | IOException e) {
//handle
}

如果我们需要获取异常的更多信息,可以使用:

12345
//获取详细的错误信息e.getMessage() //获取异常对象的实际类型e.getClass.getName()

//获取详细的错误信息
e.getMessage()

//获取异常对象的实际类型
e.getClass.getName()

``

下面对于异常的抛出有一个小小的知识点教给大家,我们通常会定义一些自定义抛出异常,这些异常的描述通常比较通俗易懂,但是对于开发人员来说,我们需要知道问题的所在,这时这个小技巧就可以起到一个很好的作用:

123456789
//抛出catch (SQLException e) {    Throwable se = new ServletExcetion("数据库错误");    se.initCause(e);    throw se;} //获取Throwable e = se.getCause();

//抛出
catch (SQLException e) {
Throwable se = new ServletExcetion("数据库错误");
se.initCause(e);
throw se;
}

//获取
Throwable e = se.getCause();

这种包装技术,既可以让用户抛出子系统中的高级异常,而且不会丢失原始异常的细节。

如果在一个方法中发生了一个受查异常,而不允许抛出它,那么包装技术就十分有用。我们可以捕获这个受查异常,并把它包装成一个运行时异常。

finally子句

当代码抛出一个异常时,就会终止方法中剩余代码的处理,并退出这个方法的执行。如果方法获得了一些本地资源,并且只有这个方法自己知道,又如果这些资源在退出方法之前必须被回收,那么就会产生资源回收问题。一种解决方案是捕获并重新抛出所有的异常。但是,这种解决方案比较乏味,这时因为需要在两个地方清除所分配的资源。一个在正常代码中,一个在异常代码中。

当然,Java给我们提供了一种更好的解决方案,那就是finally子句,下面我们来看一个例子,之后你就会对finally语句有一个非常清晰地认识:

123456789101112
try{    //1     //2,这里也许会抛出一个异常} catch (Exception e){    //3    show error message        //4} finally {    //5}//6

try{
//1


//2,这里也许会抛出一个异常

} catch (Exception e){
//3
show error message
//4
} finally {
//5
}
//6

在以上的这段的代码中,在已下3种情况下回执行finally:

  1. 代码没有抛出异常,这时程序会按照 1. 2. 5. 6的顺序执行
  2. 抛出一个在catch子句中捕获的异常,这时会分为两种情况,第一种情况是:如果在catch子句没有抛出异常,程序将执行try语句块之后的第一条语句,也就是说会按照 1. 3. 4. 5. 6的顺序去执行;第二种情况是:如果在catch子句中抛出了异常,异常将会炮灰这个方法的调用者,这里会执行 1, 3 ,5处的语句
  3. 代码跑出了一个非catch子句捕获的异常,这种情况下,会执行1. 5处的语句。

通常我们会在关闭资源或者IO流的时候去使用这个finally,以免因为异常而导致内存溢出,服务崩溃。

这里需要注意一点,当try语句和finally语句中含有return语句的时候,在方法返回前,finally子句的内容会被执行,而且,如果在finally子句中也有一个return语句,这个返回值将会覆盖原始的返回值。

当我们使用带资源的try语句时,使用finally也许会造成一个异常被覆盖的问题,即try语句块中会抛出某个异常,执行finally语句块中跑出了同样的异常,这样的话就会导致原有的异常会丢失,转而抛出的finally语句中的异常。这时我们可以使用带资源的try语句来处理(前提是这个资源实现了AutoCloseable接口的类):

123
try(Resource res = ...) {    //TODO:res run}

try(Resource res = ...) {
//TODO:res run
}

这里的try块在退出的时候,会自动的去调用res.close(),这样做即实现了finally的效果,又可以将原有代码块的异常抛出,而抑制close方法抛出的异常

Tips

异常的知识,到这里就告一段落了,最后告诉大家几点需要注意的事项,在以后的使用中可以更加的得心应手:

  1. 异常处理不能代替简单的测试!,只有在异常情况下使用异常机制,因为捕获异常需要耗费大量的时间和资源。
  2. 不过过分的细化异常(注意粒度)
  3. 利用异常层次结构。
  4. 不要压制异常
  5. 在检测错误时,“苛刻”要比放任来的更加合适(早抛出)
  6. 不要羞于传递异常(晚捕获)
本人花费半年的时间总结的《Java面试指南》已拿腾讯等大厂offer,已开源在github ,欢迎star!

本文GitHub https://github.com/OUYANGSIHAI/JavaInterview 已收录,这是我花了6个月总结的一线大厂Java面试总结,本人已拿大厂offer,欢迎star

原文链接:blog.ouyangsihai.cn >> Java基础系列(三十一)——异常


 上一篇
Java虚拟机-对象的创建和访问 Java虚拟机-对象的创建和访问
来源:黑色很白cnblogs.com/heisehenbai/p/7862660.html 一、对象的创建 创建对象在java上面是很简单的,使用new关键字就可以了,但是其实在虚拟机中,java对象
下一篇 
学习Java,你必须分清对象和对象引用! 学习Java,你必须分清对象和对象引用!
今天要说的对象和对象引用,我觉得是非常重要的一个知识点,而且,学习java,你必须搞清楚这两个概念,真的很重要! 庆哥: 你知道什么是对象吗? 小白: 对象?女朋友吗? 庆哥: 唉,好无奈啊,毕竟我不是单身狗,我是说面向对象语言