Loading

35.Java异常处理机制学习(基本概念及用法)

1.异常概述

程序员的理想世界中,用户输入的数据的格式永远都是正确的,选择文件都是存在的,代码永远不会出现bug。
但在现实世界中,却往往充满了不良的数据和有问题的代码,而这些问题产生的结果我们称之为异常,
英文名为Excepiton。

1.1 遇到异常我们应该怎么做?

我们首先需要明白异常会出现在那些地方?
大致可以分为三个阶段:

  • 开发期 : 在这个时期,我们应该尽可能的处理出现的异常,这个时期出现的异常风险最小,能够及时的被修复。
  • 测试期 : 这个时期出现的异常往往是测试人员测试过程中出现的异常,这个异常根据类型可以分为:系统异常,业务异常,系统异常比较容易排查问题,修复,业务异常需要开发人员深入分析进行问题排查。
  • 运行期 : 如果这个时期出现了异常,那么就意味着出大问题了!!! 希望我们永远不要遇到。。。

那么,我们该如何去让我们的系统不出现异常呢?
这就是我们学习的重点了,Java提供了一个异常处理机制可以帮助我们尽可能的减少异常所带来的损失。

2.异常处理机制学习

2.1.处理错误

一般对于用户而言,出现错误时,程序能够采取合理的行为,如果由于出现错误而使得某些操作没有完成,那么我们程序应该:
返回到一种安全的状态,并能够让用户执行其他的命令。
允许用户保存所有工作的结果,并以妥善的方式终止程序。
异常处理的任务就是将控制权从产生错误的地方转移到能够处理这些错误的错误处理器中。

2.2 常见的错误

2.2.1 用户输入错误

除了那些不可避免的键盘输入错误之外,有些用户不喜欢遵守要求输入。
例如假设有一个用户请求连接一个URL,而这个URL语法却不正确,如果我们的代码中没有进行检查,那么网络层就会报错。(这种错误我们可以选择在前端过滤解决掉,也可以选择在后端过滤解决)

2.2.2 设备错误

当硬件出现问题时,我们会发生设备错误。(这种错误大部分无法解决,只能提示用户)

2.2.3 物理限制

比如磁盘已经满了。(这个问题采用动态内部处理,也可以提示用户)

2.2.4 代码错误

这个就是你的编程问题了。(这个问题的解决方案就是提高自己的编程水平)

注意:在java中,如果某个方法不能够采用正确的方式完成它的任务,可以通过另外一个途径退出方法,在这种情况下,方法并不返回任何值,而是抛出一个封装了错误信息的对象,然后方法立即退出并不会返回任何值,此时程序也不会从调用这个方法的代码继续执行,而是异常处理机制开始搜索能够处理这种异常状况的异常处理器来处理异常,如果没有找到,此线程直接结束。

2.3 异常分类

我们通过前面的学习知道,程序发生异常时会抛出一个对象,我们称之为异常对象,异常对象分很多种,在Java程序设计语言中,异常对象都继承于Throwable类。
结构图大致如下:
在这里插入图片描述

从图中我们可以看出:所有的异常类都是由Throwable扩展而来。
第二层分为Error和Exception类:

2.3.1 Error类

Error类层次结构描述了java运行时系统的内部错误和资源耗尽错误。我们的应用程序不应该抛出这种错误,如果出现了这种错误,处理通知用户,并尽力妥善终止程序之外,我们几乎无能为力。

2.3.2 Exception

在设计java程序时,要重点关注Exception,我们看到它又被分为了两类:
一般规则是:
由编程错误导致的异常属于RuntimeException;
如果程序没有问题,但由于像I/O错误这类问题导致的异常属于其他异常(IOException)

  • 继承于RuntimeException的异常例子:
    +错误的强制类型转换
    在这里插入图片描述

    +数组访问越界
    在这里插入图片描述

    +访问null指针
    在这里插入图片描述

  • 其他异常例子:
    +试图打开一个不存在的文件
    在这里插入图片描述

+试图根据给定的字符串查找Class对象,而这个字符串表示的类不存在
在这里插入图片描述

注意:如果出现了RuntimeException,那么就一定是我们代码的问题,这时我们就要检查代码是否编写正确。

2.3.3 检查型异常和非检查型异常

在Java语言中,将继承于Error类或RuntimeException的所有异常称之为非检查型异常(unchecked),对于非检查型异常,我们无法捕获。
其他所有的异常称之为检查型异常(checked),对于检查型异常我们需要根据自己的需求去进行异常捕获处理。

注意:编译器将会检查我们是否为所有的检查型异常提供了异常处理器。

2.4 检查型异常

如果遇到了无法处理的情况,Java方法可以抛出一个检查型异常来让调用者捕获。
一个合格的方法不仅要告诉调用者将要返回什么值,还要能在必要的时候告诉调用者发生了什么错误。

如何实现呢? 对于返回值,我们都知道用个return 就可以了,那么对于检查型异常呢?
要让方法抛出发生的错误,我们需要在方法的首部指出这个方法可能抛出的错误,所以要修改方法的首部,以反映这个方法可能抛出的异常:
一个方法可以声明一个或多个异常
像FileInputStream的构造方法声明了一个异常

public FileInputStream(String name) throws FileNotFoundException {
    this(name != null ? new File(name) : null);
}

我们也可以为一个方法声明两个没有包含关系的异常
例如:

/**
 * 这个方法将在发生对应的异常时将其抛出
 **/
public static void IoException1() throws IOException, ClassNotFoundException {
    File file = new File("noEx");
    InputStream inputStream = new FileInputStream(file);
    inputStream.read();
    Class cl = Class.forName("java");
}

在编写方法时,不必声明这个方法的所有异常,至于什么时候需要在方法中用throws声明异常,以及要用throws子句声明那些异常,需要记住下面的4种情况:

  • 调用了一个抛出检查型异常的方法。
  • 检测到一个错误,并且利用throw语句抛出一个检查型异常。
  • 程序出现错误。
  • Java虚拟机或运行库出现内部错误。

如果遇到前两种情况,则必须告诉调用这个方法的调用者有可能抛出异常。
对于后两种情况,我们需要去排查原因,然后优化代码去避免它。
注意:不可以声明非检查型异常。 总之,一个方法必须声明所有可能抛出的的检查型异常,而非检查型异常要么在我们的控制外,要么从一开始就避免的

2.5 抛出异常

前面我们只在方法中声明了异常是不够的,还需要抛出它,这样异常处理器才能获取该异常并进行处理。
抛出其实非常简单:在必要时刻抛出一个对象就可以了

/**
 * 抛出异常
 */
public static void IoException2() throws IOException, ClassNotFoundException {
    File file = new File("noEx");
    try {
        InputStream inputStream = new FileInputStream(file);
        inputStream.read();
    }catch (FileNotFoundException fileNotFoundException){
        throw new FileNotFoundException("未找到文件");
    }catch (IOException e){
        e.printStackTrace();
        throw new IOException("文件读取");
    }
    
    try {
        Class cl = Class.forName("java");
    }catch (ClassNotFoundException e){
        throw new ClassNotFoundException("未找到类文件java.class");
    }
    
}

通过前面我们可以看到,如果一个已有的异常类能够满足你的要求,抛出这个异常非常容易:

  • 找到一个合适的异常类
  • 创建这个类的对象
  • 将对象抛出
public static void main(String[] args) {
    try {
        IoException2();
    }catch (Exception e){
        System.out.println(e.getMessage());
    }
}

注意:一旦抛出了异常,这个方法就不会返回结果给调用者,而是将异常对象返回给调用者的异常处理器,如果调用者没有做异常捕获处理,则必须抛出这个异常给上层调用者。

2.6 创建异常类

如果没有合适的异常类满足我们的要求呢?这时我们可以自己创建一个异常类来满足我们的需求。
我们的代码可能会遇到任何异常类都无法描述的问题,这时,我们就可以定义一个继承于Exception的类或继承于它的任何子类的类即可。
例如:

class FileFormatException extends IOException{
  public FileFormatException() { }
  public FileFormatException(String gripe){
    super(gripe);
  }
}

创建自定义异常类的习惯做法是,包含两个构造器:一个是默认的构造器,另一个是包含详细信息的构造器(超类Throwable的toString方法会返回一个字符串,其中包含这个详细信息,这在调试中非常有用)

演示Demo:

import java.io.IOException;

public class FileFormatException extends IOException {

    public FileFormatException(){}

    public FileFormatException(String s){
        super(s);
    }

    public static void main(String[] args) {
        try{
            throwFiledFormatException("File:fff");
        }catch (IOException e){
            e.printStackTrace();
        }
    }

    public static void throwFiledFormatException(String file) throws FileFormatException {
        if(file.contains(":")){
            throw new FileFormatException("file 不能包含:字符");
        }
    }
}

在这里插入图片描述

2.7 捕获异常

如果发生了异常,但没有任何地方捕获这个异常,程序就会终止,并在控制台上打印一个消息,其中包括这个异常的类型和一个堆栈轨迹。
如果我们想让我们的程序不随意强退,我们就要捕获检查型异常来避免非正常终止。

2.7.1 捕获一个异常

try{
   执行代码
}catch(异常类型 e){
   如何处理异常
}

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

  • 程序将跳过try语句块的其余代码
  • 程序将执行catch子句中的处理器代码
    如果try语句块中的代码没有抛出任何异常,那么程序将跳过catch子句。

注意:如果方法中的任何代码抛出了catch子句中没有声明的一个异常类型,那么这个方法就会立即退出(这时我们只能希望它的调用者为这种类型提供了catch子句)

2.7.2 捕获多个异常

例子:

try{

}catch(FileNotFoundException e){

}catch(IOException e){

}

一般来说,捕获多个异常时,范围小的异常在前面。
异常对象可能包含有关异常性质的信息,要想获得这个对象的更多信息,我们可以尝试使用:
e.getMessage();得到详细的错误信息,或者可以使用e.getClass().getName();来获得异常对象的实际类型。

2.7.3 并列异常捕获(要求catch里的异常不可有包含关系)

try{

}catch(FileNotFountException | UnknownHostException e){
 
}

2.7.4 异常链

try{

}catch(SQLException e){
  throw new ServletException("database error " + e.getMessage());
}

然后更外层的方法捕获抛出的这个异常从而形成异常链。

对于异常链有一种更好的方法,可以把原始异常设置为新异常的 “原因” :

try{

}catch(SQLException original){
  Exception e = new ServletException("datebase error");
  e.initCause(original);
  throw e;
}

捕获到这个异常时,可以使用下面的这条语句获取原始异常:

Throwable original = caughtException.getCause();

注意:如果在一个方法中发生了一个检查型异常,但这个方法不允许抛出检查型异常,那么包装技术也很有用,我们可以捕获这个检查型异常,并将它包装成一个运行时异常。

总结:正确认识异常类型,以及定义异常,声明异常,捕获异常的意义。

3.代码地址

Java基础学习/src/main/java/Progress/exa35 · 严家豆/Study - 码云 - 开源中国 (gitee.com)

posted @ 2020-11-23 16:25  文牧之  阅读(51)  评论(0)    收藏  举报  来源