第12章 通过异常处理错误

异常处理机制

Java提出一种假设,如果程序可以顺利执行,那就一切正常,把系统的业务实现代码放在 try 块中定义,所有的异常处理逻辑放到 catch 块中处理。

try{
//业务实现代码
......
}
catch (Exception e){
//异常处理
......
}

如果执行 try 块里的业务逻辑代码出现异常,系统自动生成一个异常对象,该异常对象被提交给Java运行时环境,这个过程被称为抛出(throw)异常。当Java运行时环境收到异常对象时,会寻找处理该异常对象的catch块,如果找到合适的catch块并把该异常对象交给catch块处理,那个过程被称为捕获(catch)异常;==如果Java运行环境找不到捕获异常的catch块,则运行时环境终止,Java程序也将退出。==当捕获异常以后,可以处理这个异常,使得程序继续执行,也可以退出程序。
在这里插入图片描述

异常类的继承体系

在这里插入图片描述

  • Java把所有非正常情况分为两种:异常(Exception)和错误(Error)。
    Error错误,一般是指虚拟机相关的问题,如系统奔溃、动态连接失败、虚拟机出现错误等。这种错误无法恢复或不可能捕获,将导致应用程序中断。通常,应用程序无法处理这些错误,因此,应用程序不应该使用catch块来捕获Error对象。在定义该方法时,也无须在其throws子句中声明该方法可能抛出Error及其任何子类。
    Exception异常,是程序本身可以处理的异常,包括RuntimeException等运行时异常(Unchecked 异常),如NullPointerException(空指针异常)、IndexOutOfBoundsException(下标越界异常)等,这些异常是不检查异常,程序中可以选择捕获处理,也可以不处理。这些异常一般是由程序逻辑错误引起的,程序应该从逻辑角度尽可能避免这类异常的发生。
    非运行时异常(编译异常) 包括:RuntimeException以外的异常,类型上都属于Exception类及其子类。从程序语法角度讲是必须进行处理的异常,如果不处理,程序就不能编译通过。如IOException、SQLException等以及用户自定义的Exception异常,一般情况下不自定义检查异常
  • 运行时异常

捕获异常时,一定要先捕获小异常,再捕获大异常。

package com.chao.chapterSixteen;

import java.util.Date;

public class TestNull {
    public static void main(String[] args) {
        Date d = null;
        try {
            System.out.println(d.after(new Date()));
        } catch (NullPointerException ne) {
            System.out.println("空指针异常"); //小异常
        } catch (Exception e) {
            System.out.println("未知异常"); //大异常
        }
    }
}


访问异常信息

如果程序需要再 catch 块中访问异常对象的相关信息,可以通过调用 catch 块后异常形参的方法来获得。
所有异常对象都包含如下几个常用方法:

  • getMessage():返回该异常的详细描述字符串
  • printStackTrace():将该异常的跟踪栈信息输出到标准输出
  • printStackTrace(PrintStream s):将该异常的跟踪栈信息输出到指定输出流
  • getStackTrack():返回该异常的跟踪栈信息
package com.chao.chapterSixteen;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;

public class AccessExceptionMsg {
    public static void main(String[] args) {
        try {
            FileInputStream fis = new FileInputStream("a.txt");
        } catch (IOException ioe) {
            System.out.println(ioe.getMessage());
            ioe.printStackTrace();
        }
    }
}

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

使用 finally 回收资源

有些时候,程序再 try 块中打开了一些物理资源(例如数据库的连接、网络的连接和磁盘文件等),这些物理资源都必须显示地回收。Java垃圾回收机制不会回收任何物理资源,垃圾回收机制只堆内存中对象所占用的内存。为了保证 try 块中打开的物理资源一定得到回收,异常处理机制提供了 finally 块。不管 try 块中的代码是否出现异常,也不管哪一个 catch 块被执行,finally 块总会被执行。


Checked 异常和 Runtime 异常体系

什么是 Checked 异常?
什么是 Runtime 异常?

  1. 对于Checked异常的处理方式有两种:
    • 当前方法明确知道如何处理该异常,程序应该使用 try … catch 块来捕获该异常
    • 当前方法不知道如何处理该异常,应该在定义方法的时声明抛出该异常
使用throws 声明抛出异常
  • 使用 throws 声明抛出异常的思路是:当前方法不知道应该如何处理这种类型的异常,该异常应该由上一级调用者处理。如果 main 方法也不知道如何处理这种类型的异常,也可以使用 throws 声明抛出异常,该异常将交给 JVM 处理。JVM 对异常的处理方法是:打印异常跟踪栈信息,并终止程序运行,这就是前面程序遇到异常自动结束的原因。
  • 如果某段代码中调用了一个带 throws 声明的方法,该方法声明抛出了 Checked 异常,这表明该方法希望它的调用者来处理该异常。这表明,这段代码要么放在 try 块中显式地捕获该异常;要么这段代码处在另一个带 throws 声明的抛出的方法中。
  • 使用 throws 声明抛出异常时有一个小限制:子类方法中声明抛出的异常类型应该是父类方法中声明抛出的异常类型的子类或相等
package com.chao.chapterSixteen;

import java.io.IOException;

public class OverrideThrows {
    public void test() throws IOException {

    }
}

class Sub extends OverrideThrows {
    //下面代码无法同构编译:子类方法声明抛出了比父类方法更大的异常
    public void test() throws Exception {

    }
}

使用 throw 抛出异常

当程序出现错误时,系统会自动抛出异常;除此之外 Java 也允许程序自行抛出异常,自行抛出异常使用 throw 语句完成。
这里的“异常”时一种很主观的做法,是否抛出异常,可能根据业务需求来决定。如果需要程序自行抛出异常,应该使用 throw 语句,throw 语句抛出的不是异常类,而是一个异常实例,而且每次只抛出一个异常实例。

自定义异常类

用户自定义异常类都应该继承 Exception 基类,如果希望自定义 Runtime 异常,则应该继承 RuntimeException 基类。定义异常时通常需要提供两种构造器:一个是无参数的构造器;另一个是带有一个字符串的构造器,这个字符串将作为该异常对象的详细说明。

package com.chao.chapterSixteen;

public class AuctionException extends Exception {
    public AuctionException() {}

    public AuctionException(String message) {
        super(message);
    }
}

catch 和 throw 同时使用

前面介绍异常的处理方法有两种:

  • 在出现异常的方法内捕获并处理异常,该方法的调用者将不能再次捕获该异常
  • 该方法签名中声明抛出该异常,将该异常完全交给方法调用者处理
    但是实际应用中有着更复杂的处理方式:当一个异常出现时,单靠某个方法无法完全处理该异常,必须由几个方法协作才能完全处理该异常。也就是说,异常出现的当前方法中,程序只对异常进行部分处理,还有些处理需要在该方法的调用者中才能完成,所以应当再次抛出异常,这样可以让方法的调用者也能捕获到异常。

异常链

这种把捕获一个异常然后接着抛出另一个异常,并把原始异常信息保存下来的是一种典型的链式处理,称为“异常链”。

Java 的异常跟踪栈

异常对象的 printStackTrace 方法用于打印异常的跟踪栈信息,根据 printStackTrace 方法的输出结果,我们可以找到异常的源头,并跟踪到异常一路触发的过程。

package com.chao.chapterSixteen;

class SelfException extends Exception {
    public SelfException() {}

    public SelfException(String message) {
        super(message);
    }
}

public class TestPrintStackTrace {
    public static void main(String[] args) throws SelfException {
        firstMethod();
    }

    public static void firstMethod() throws SelfException {
        secondMethod();
    }

    public static void secondMethod() throws SelfException {
        thiredMethod();
    }

    public static void thiredMethod() throws SelfException {
        throw new SelfException("自定义异常信息");
    }
}

Exception in thread “main” com.chao.chapterSixteen.SelfException: 自定义异常信息 //详细显示了异常的类型和异常的详细信息
at com.chao.chapterSixteen.TestPrintStackTrace.thiredMethod(TestPrintStackTrace.java:25) //异常源头
at com.chao.chapterSixteen.TestPrintStackTrace.secondMethod(TestPrintStackTrace.java:21) //第二个
at com.chao.chapterSixteen.TestPrintStackTrace.firstMethod(TestPrintStackTrace.java:17) //第三个
at com.chao.chapterSixteen.TestPrintStackTrace.main(TestPrintStackTrace.java:13) //main方法
它记录了应用程序中执行停止的各个点:
第一行的信息详细显示了异常的类型和异常的详细信息。接下来跟踪栈记录程序中所有的异常发生点,各行显示被调用方法中执行的停止位置,并标明类、类中的方法名与故障点对应的文件的行。一行行地往下看,跟踪栈总是***最内部的被调用的方法逐渐上传,知道最外部业务操作的起点***

面向对象的应用程序运行时,经常会发生一系列方法调用,从而形成方法调用栈。异常的传播则相反:只要异常没有完全被捕获(包括异常没有被捕获,或异常没有被处理后重新抛出了新异常),异常从发生异常的方法逐渐向外传播,首先传给该方法的调用者,该方法的调用者再次传给其调用者… 直至最后传到 main 方法,如果 main方法依然没有处理该异常,JVM 会终止该程序并打印异常的跟踪栈信息。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值