Java 异常处理

参考资料

[1]. 疯狂Java讲义(第三版) 李刚
[2]. The try-with-resources Statement,
https://docs.oracle.com/javase/tutorial/essential/exceptions/tryResourceClose.html

异常处理机制

异常关键字

Java的异常机制主要依赖于try、catch、finally、throw、throws五个关键字

  • try关键字后紧跟一个花括号括起来的代码块(花括号不可省略),简称try块,它里面放置可能引发异常的代码。
  • catch后对应异常类型和一个代码块,用于表名该catch块用于处理这种类型的代码块,catch块可以有多个,catch的异常范围应该是从小到大。
  • finally块在catch块后面,用于显式回收在try块里打开的物理资源,异常机制会保证finally块一定被执行。
  • throws 关键字主要在方法签名中使用,用于声明该方法可能抛出的异常。
  • throw 可以单独作为语句使用,抛出一个具体的异常对象。

异常的继承体系

Java把所有的非正常情况分成两种:异常(Exception)和错误(Error),它们都继承了Throwable父类。
这里写图片描述
Error错误,一般是指与虚拟机相关的问题,如系统崩溃、虚拟机错误、动态链接失败等,所以我们的关注对象是Exception异常。

Exception异常

进行异常捕获时不仅应该把Exception类对应的catch块放在最后,而且所有父类异常的catch块都应该排在子类异常catch块的后面,简单的说就是,先处理小异常,再处理大异常,否则会出现编译错误。

示例1

int[] a = {1,2,3};
try
{
    int c = a[0] / a[3];
    System.out.println("您输入的两个数相除的结果是:" + c );
}
catch (IndexOutOfBoundsException ie)
{
    System.out.println("数组越界");
}
catch (NumberFormatException ne)
{
    System.out.println("数字格式异常:程序只能接受整数参数");
}
catch (ArithmeticException ae)
{
    System.out.println("算术异常");
}
catch (Exception e)
{
    System.out.println("未知异常");
}

捕获空指针异常和其他异常

Date d = null;
try
{
    System.out.println(d.after(new Date()));
}
catch (NullPointerException ne)
{
    System.out.println("空指针异常");
}
catch(Exception e)
{
    System.out.println("未知异常");
}

Java 7 提供的多异常捕获

在Java 7 以前,每个catch块只能捕获一种类型的异常;但从Java 7 开始,一个catch块可以捕获多种类型的异常。
注意事项:
1. 多种异常类型之间用竖线(|)隔开。
2. 异常变量有隐式的final修饰,因此程序不能对异常变量重新赋值。
示例程序:

try
{
    int a = Integer.parseInt(args[0]);
    int b = Integer.parseInt(args[1]);
    int c = a / b;
    System.out.println("您输入的两个数相除的结果是:" + c );
}
catch (IndexOutOfBoundsException|NumberFormatException
    |ArithmeticException ie)
{
    System.out.println("程序发生了数组越界、数字格式异常、算术异常之一");
    // 捕捉多异常时,异常变量默认有final修饰,
    // 所以下面代码有错:
    ie = new ArithmeticException("test");  // ①
}
catch (Exception e)
{
    System.out.println("未知异常");
    // 捕捉一个类型的异常时,异常变量没有final修饰
    // 所以下面代码完全正确。
    e = new RuntimeException("test");    // ②
}

访问异常信息

所有的异常对象都包含了如下几个常用方法:
1. getMessage():返回该异常的详细描述字符串。
2. printStackTrace():将该异常的跟踪栈信息输出到标准错误输出。
3. printStackTrace(PrintSream s):将该异常的跟踪栈信息输出到标准错误输出。
4. getStackTrace():返回该异常的跟踪栈信息。

try
{
    FileInputStream fis = new FileInputStream("a.txt");
}
catch (IOException ioe)
{
    System.out.println(ioe.getMessage());
    ioe.printStackTrace();
}

使用finally回收资源

由于Java的垃圾回收机制不会回收任何物理资源,垃圾回收机制只能回收堆内存中对象所占用的内存。所以需要显示回收例如数据库连接、网络连接和磁盘文件等物理资源。

FileInputStream fis = null;
try
{
    fis = new FileInputStream("a.txt");
}
catch (IOException ioe)
{
    System.out.println(ioe.getMessage());
    // return语句强制方法返回
    return ;       // ①
    // 使用exit来退出虚拟机
    // System.exit(1);     // ②
}
finally
{
    // 关闭磁盘文件,回收资源
    if (fis != null)
    {
        try
        {
            fis.close();
        }
        catch (IOException ioe)
        {
            ioe.printStackTrace();
        }
    }
    System.out.println("执行finally块里的资源回收!");
}

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

public class FinallyFlowTest
{
    public static void main(String[] args)
        throws Exception
    {
        boolean a = test();
        System.out.println(a);
    }
    public static boolean test()
    {
        try
        {
            // 因为finally块中包含了return语句
            // 所以下面的return语句失去作用
            return true;
        }
        finally
        {
            return false;
        }
    }
}

Java 7 的自动关闭资源的try语句

Java 7 增强了try语句的功能,它允许在try关键字后紧跟一对圆括号,圆括号可以声明、初始化一个或多个资源,此处的资源指的是那些必须在程序结束时显示关闭的资源(比如数据库连接、网络连接等),try语句在该语句结束时自动关闭这些资源。

实现条件

Closeable是AutoCloseable的子接口,可以被自动关闭的资源类要么实现AutoCloseable接口,要么实现Closeable接口。
Closeable接口里的close()方法声明抛出了IOException,因此它的实现类在实现close()方法时只能声明抛出IOException或其子类。
AutoCloseable接口里的close()方法声明抛出了Exception,因此它的实现类在实现close()方法时可以声明抛出任何异常。
代码示例1:

public class AutoCloseTest
{
    public static void main(String[] args)
        throws IOException
    {
        try (
            // 声明、初始化两个可关闭的资源
            // try语句会自动关闭这两个资源。
            BufferedReader br = new BufferedReader(
                new FileReader("AutoCloseTest.java"));
            PrintStream ps = new PrintStream(new
                FileOutputStream("a.txt")))
        {
            // 使用两个资源
            System.out.println(br.readLine());
            ps.println("庄生晓梦迷蝴蝶");
        }
    }
}

BufferedReader类和PrintStream类都实现了Closeable接口,它们放在try语句中声明、初始化的话,try语句会自动关闭它们。
自动关闭资源的try语句相当于包含了隐式的finally块(这个finally块用于关闭资源),因此这个try语句可以既没有catch块,也没有finally块。如果有需要,也可以带catch和finally块。

Checked异常和Runtime异常体系

Java的异常被分为两大类,Checked异常和Runtime异常(运行时异常)。

使用throws声明抛出异常

throws声明用在方法签名之后,一旦使用throws语句声明抛出该异常,程序就无须使用try..catch块来捕获该异常了。
语法如下:

throws ExceptionClass1, ExceptionClass1...

最简单的示例

public class ThrowsTest
{
    public static void main(String[] args)
            throws IOException
    {
        FileInputStream fis = new FileInputStream("a.txt");
    }
}

调用一个带throws的方法

public class ThrowsTest2
{
    public static void main(String[] args)
        throws Exception
    {
        // 因为test()方法声明抛出IOException异常,
        // 所以调用该方法的代码要么处于try...catch块中,
        // 要么处于另一个带throws声明抛出的方法中。
        test();
    }
    public static void test()throws IOException
    {
        // 因为FileInputStream的构造器声明抛出IOException异常,
        // 所以调用FileInputStream的代码要么处于try...catch块中,
        // 要么处于另一个带throws声明抛出的方法中。
        FileInputStream fis = new FileInputStream("a.txt");
    }
}

子类方法抛出的异常不能超过父类

public class OverrideThrows
{
    public void test()throws IOException
    {
        FileInputStream fis = new FileInputStream("a.txt");
    }
}
class Sub extends OverrideThrows
{
    // 子类方法声明抛出了比父类方法更大的异常
    // 所以下面方法出错
    public void test()throws Exception
    {
    }
}

使用throw抛出异常

抛出异常

如果需要在程序中自行抛出异常,则应使用throw语句,throw语句可以单独使用,throw语句抛出的不是异常类,而是一个异常实例,而且每次只能抛出一个异常实例。
如果throw语句抛出的异常是Checked异常,则该throw语句要么处于try块,显式捕获该异常,要么放在一个带throws声明抛出的方法中,即把该异常交给该方法的调用者处理;如果throw语句抛出的异常是Runtime异常,则无限制。

public class ThrowTest
{
    public static void main(String[] args)
    {
        try
        {
            // 调用声明抛出Checked异常的方法,要么显式捕获该异常
            // 要么在main方法中再次声明抛出
            throwChecked(-3);
        }
        catch (Exception e)
        {
            // 调用后处理异常
            System.out.println(e.getMessage());
        }
        // 调用声明抛出Runtime异常的方法既可以显式捕获该异常,
        // 也可不理会该异常
        throwRuntime(3);
    }
    public static void throwChecked(int a)throws Exception
    {
        if (a > 0)
        {
            // 自行抛出Exception异常
            // 该代码必须处于try块里,或处于带throws声明的方法中
            throw new Exception("a的值大于0,不符合要求");
        }
    }
    public static void throwRuntime(int a)
    {
        if (a > 0)
        {
            // 自行抛出RuntimeException异常,既可以显式捕获该异常
            // 也可完全不理会该异常,把该异常交给该方法调用者处理
            throw new RuntimeException("a的值大于0,不符合要求");
        }
    }
}

自定义异常类

自定义异常都应该继承Exception基类,如果希望自定义Runtime异常,则应该继承RuntimeException基类。

public class AuctionException extends Exception
{
    // 无参数的构造器
    public AuctionException(){}       //①
    // 带一个字符串参数的构造器
    public AuctionException(String msg)    //②
    {
        super(msg);
    }
}

catch和throw同时使用

异常处理方式有如下两种:
1. 在出现异常的方法内捕获并处理异常,该方法的调用者将不能再次捕获该异常。
2. 该方法签名中声明抛出该异常,将该异常完全交给方法调用者来处理。
现在结合两者一起使用,这样既能处理异常,又能继续抛出一个异常告诉调用者。

public class AuctionTest
{
    private double initPrice = 30.0;
    // 因为该方法中显式抛出了AuctionException异常,
    // 所以此处需要声明抛出AuctionException异常
    public void bid(String bidPrice)
        throws AuctionException
    {
        double d = 0.0;
        try
        {
            d = Double.parseDouble(bidPrice);
        }
        catch (Exception e)
        {
            // 此处完成本方法中可以对异常执行的修复处理,
            // 此处仅仅是在控制台打印异常跟踪栈信息。
            e.printStackTrace();
            // 再次抛出自定义异常
            throw new AuctionException("竞拍价必须是数值,"
                + "不能包含其他字符!");
        }
        if (initPrice > d)
        {
            throw new AuctionException("竞拍价比起拍价低,"
                + "不允许竞拍!");
        }
        initPrice = d;
    }
    public static void main(String[] args)
    {
        AuctionTest at = new AuctionTest();
        try
        {
            at.bid("df");
        }
        catch (AuctionException ae)
        {
            // 再次捕捉到bid方法中的异常。并对该异常进行处理
            System.err.println(ae.getMessage());
        }
    }
}

Java 7 增强的throw语句

Java 7以后Java编译器可以自我辨识抛出的异常类型

public class ThrowTest2
{
    public static void main(String[] args)
        // Java 6认为①号代码可能抛出Exception,
        // 所以此处声明抛出Exception
//      throws Exception
        // Java 7会检查①号代码可能抛出异常的实际类型,
        // 因此此处只需声明抛出FileNotFoundException即可。
        throws FileNotFoundException
    {
        try
        {
            new FileOutputStream("a.txt");
        }
        catch (Exception ex)
        {
            ex.printStackTrace();
            throw ex;        // ①
        }
    }
}

异常链

String构造器用来向外传递异常,Throwable 构造器用来向内传递异常。这样用户和调用程序的程序员都可以看到不同的异常。

public class SalException extends Exception
{
    public SalException(){}
    public SalException(String msg)
    {
        super(msg);
    }
    // 创建一个可以接受Throwable参数的构造器
    public SalException(Throwable t)
    {
        super(t);
    }
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值