Java Exception - 使用try-catch捕获异常

2年前 (2022) 程序员胖胖胖虎阿
398 0 0

文章目录

    • 1. 异常处理机制
    • 2. 使用try...catch捕获异常
    • 3. 先捕获小异常,再捕获大异常
    • 4. 访问异常信息
    • 5. 使用finally回收资源

1. 异常处理机制

Java的异常处理机制可以让程序具有极好的容错性,让程序更加健壮。当程序运行出现意外情形时,系统会自动生成一个Exception对象来通知程序,从而实现将“业务功能实现代码”和“错误处理代码”分离,提供更好的可读性。

2. 使用try…catch捕获异常

如果程序可以顺利完成,那就“一切正常”,把系统的业务实现代码放在try块中定义,所有的异常处理逻辑放在catch块中进行处理。下面是Java异常处理机制的语法结构:

public class ExceptionTest {
    public static void main(String[] args) {
        try{
            // 业务代码
        }catch (Exception e){
            // 出现异常
        }
    }
}

如果执行try块里的业务逻辑代码时出现异常,系统自动生成一个异常对象,该异常对象被提交给Java运行时环境,这个过程被称为抛出(throw)异常。

当Java运行时环境收到异常对象时,会寻找能处理该异常对象的catch块,如果找到合适的catch块,则把该异常对象交给该catch块处理,这个过程被称为捕获(catch)异常;

如果Java运行时环境找不到捕获异常的catch块,则运行时环境终止,Java程序也将退出。

注意:

不管程序代码块是否处于try块中,甚至包括catch块中的代码,只要执行该代码块时出现了异常,系统总会自动生成一个异常对象。如果程序没有为这段代码定义任何的catch块,则Java运行时环境无法找到处理该异常的catch块,程序就在此退出。

3. 先捕获小异常,再捕获大异常

当Java运行时环境接收到异常对象时,如何为该异常对象寻找catch块呢?

当Java运行时环境接收到异常对象后,会依次判断该异常对象是否是catch块后异常类或其子类的实例,如果是,Java运行时环境将调用该catch块来处理该异常;否则再次拿该异常对象和下一个catch块里的异常类进行比较。
Java Exception - 使用try-catch捕获异常
当程序进入负责异常处理的catch块时,系统生成的异常对象ex将会传给catch块后的异常形参,从而允许catch块通过该对象来获得异常的详细信息。

try块后可以有多个catch块,这是为了针对不同的异常类提供不同的异常处理方式。当系统发生不同的意外情况时,系统会生成不同的异常对象,Java运行时就会根据该异常对象所属的异常类来决定使用哪个catch块来处理该异常。

在通常情况下,如果try块被执行一次,则try块后只有一个catch块会被执行,绝不可能有多个catch块被执行。除非在循环中使用了continue开始下一次循环,下一次循环又重新运行了try块,这才可能导致多个catch块被执行。

@Slf4j
public class ExceptionTest {
    public static void main(String[] args) {
        try{
            int a = Integer.parseInt(args[0]);
            int b = Integer.parseInt(args[1]);
            int c = a/b;
        }catch (IndexOutOfBoundsException e){
            log.error("数组越界异常");
        }catch (NumberFormatException e){
            log.error("数字格式异常");
        }catch (ArithmeticException e){
            log.error("算数异常");
        }catch (Exception e){
            log.error("未知异常");
        }
    }
}

上面程序针对IndexOutOfBoundsException、NumberFormatException、ArithmeticException类型的异常,提供了专门的异常处理逻辑。Java运行时的异常处理逻辑可能有如下几种情形。

  • 如果运行该程序时输入的参数不够,将会发生数组越界异常,Java运行时将调用IndexOutOfBoundsException对应的catch块处理该异常。
  • 如果运行该程序时输入的参数不是数字,而是字母,将发生数字格式异常,Java运行时将调用NumberFormatException对应的catch块处理该异常。
  • 如果运行该程序时输入的第二个参数是0,将发生除0异常,Java运行时将调用ArithmeticException对应的catch块处理该异常。
  • 如果程序运行时出现其他异常,该异常对象总是Exception类或其子类的实例,Java运行时将调用Exception对应的catch块处理该异常。

我们总是把对应Exception类的catch块放在最后,这是为什么呢?如果我们把Exception类对应的catch块排在其他catch块的前面,Java运行时将直接进入该catch块(因为所有的异常对象都是Exception或其子类的实例),而排在它后面的catch块将永远也不会获得执行的机会。

实际上,进行异常捕获时不仅应该把Exception类对应的catch块放在最后,而且所有父类异常的catch块都应该排在子类异常catch块的后面(简称:先处理小异常,再处理大异常),否则将出现编译错误。
Java Exception - 使用try-catch捕获异常
进行异常捕获时,一定要记住先捕获小异常,再捕获大异常。

4. 访问异常信息

如果程序需要在catch块中访问异常对象的相关信息,则可以通过访问catch块后的异常形参来获得。当Java运行时决定调用某个catch块来处理该异常对象时,会将异常对象赋给catch块后的异常参数,程序即可通过该参数来获得异常的相关信息。

所有的异常对象都包含了如下几个常用方法。

  • getMessage():返回该异常的详细描述字符串。
  • printStackTrace():将该异常的跟踪栈信息输出到标准错误输出。
  • printStackTrace(PrintStream s):将该异常的跟踪栈信息输出到指定输出流。
  • getStackTrace():返回该异常的跟踪栈信息。
@Slf4j
public class ExceptionTest {
    public static void main(String[] args) {
        try{
            FileInputStream fileInputStream = new FileInputStream("a.txt");
        }catch (IOException e){
            System.out.println(e.getMessage());
            e.printStackTrace();
        }
    }
}

上面程序调用了Exception对象的getMessage()方法来得到异常对象的详细信息,也使用了printStackTrace()方法来打印该异常的跟踪信息。
Java Exception - 使用try-catch捕获异常

5. 使用finally回收资源

有些时候,程序在try块里打开了一些物理资源(例如数据库连接、网络连接和磁盘文件等),这些物理资源都必须显式回收。Java的垃圾回收机制不会回收任何物理资源,垃圾回收机制只能回收堆内存中对象所占用的内存。

在哪里回收这些物理资源呢?在try块里回收?还是在catch块中进行回收?假设程序在try块里进行资源回收,如果try块的某条语句引起了异常,该语句后的其他语句通常不会获得执行的机会,这将导致位于该语句之后的资源回收语句得不到执行。如果在catch块里进行资源回收,但catch块完全有可能得不到执行,这将导致不能及时回收这些物理资源。

为了保证一定能回收try块中打开的物理资源,异常处理机制提供了finally块。不管try块中的代码是否出现异常,也不管哪一个catch块被执行,甚至在try块或catch块中执行了return语句,finally块总会被执行。

@Slf4j
public class ExceptionTest {
    public static void main(String[] args) {
        FileInputStream fileInputStream = null;
        try{
            fileInputStream = new FileInputStream("a.txt");
        }catch (IOException e){
            System.out.println(e.getMessage());
            e.printStackTrace();
            return;
        }finally {
            if(fileInputStream!=null){
                try {
                    fileInputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("回收资源");
        }
    }
}

上面程序的try块后增加了finally块,用于回收在try块中打开的物理资源。注意程序的catch块中有一条return语句,该语句强制方法返回。在通常情况下,一旦在方法里执行到return语句的地方,程序将立即结束该方法;现在不会了,虽然return语句也强制方法结束,但一定会先执行finally块里的代码。

@Slf4j
public class ExceptionTest {
    public static void main(String[] args) {
        FileInputStream fileInputStream = null;
        try{
            fileInputStream = new FileInputStream("a.txt");
        }catch (IOException e){
            System.out.println(e.getMessage());
            e.printStackTrace();
            // 在异常处理的catch块中使用System.exit(1)语句来退出虚拟机
            System.exit(1);
        }finally {
            if(fileInputStream!=null){
                try {
                    fileInputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("回收资源");
        }
    }
}

上面执行结果表明finally块没有被执行。如果在异常处理代码中使用System.exit(1)语句来退出虚拟机,则finally块将失去执行的机会。

除非在try块、catch块中调用了退出虚拟机的方法,否则不管在try块、catch块中执行怎样的代码,出现怎样的情况,异常处理的finally块总会被执行。

在通常情况下,不要在finally块中使用如return或throw等导致方法终止的语句,一旦在finally块中使用了return或throw语句,将会导致try块、catch块中的return、throw语句失效。

@Slf4j
public class ExceptionTest {
    public static void main(String[] args) {
        // false
        System.out.println(test());
    }
    private static boolean test() {
        try {
            // 因为finally语句中使用了return语句,因此try中的return语句失效
            return true;
        }finally {
            return false;
        }
    }
}

当Java程序执行try块、catch块时遇到了return或throw语句,这两个语句都会导致该方法立即结束,但是系统执行这两个语句并不会结束该方法,而是去寻找该异常处理流程中是否包含finally块:

  • 如果没有finally块,程序立即执行return或throw语句,方法终止。
  • 如果有finally块,系统立即开始执行finally块——只有当finally块执行完成后,系统才会再次跳回来执行try块、catch块里的return或throw语句。
  • 如果finally块里也使用了return或throw等导致方法终止的语句,finally块已经终止了方法,系统将不会跳回去执行try块、catch块里的任何代码。

尽量避免在finally块里使用return或throw等导致方法终止的语句,否则可能出现一些很奇怪的情况。

版权声明:程序员胖胖胖虎阿 发表于 2022年11月24日 上午1:08。
转载请注明:Java Exception - 使用try-catch捕获异常 | 胖虎的工具箱-编程导航

相关文章

暂无评论

暂无评论...