Java基础——异常

异常是什么?

程序不可能是没有错误的,当程序遇到问题时,会抛出异常,此时应终止程序,或处理异常。

异常层次

Throwable是所有异常的父类,往下为
在这里插入图片描述

  • Error:运行时系统的内部错误和资源消耗尽错误,不会由应用程序产生
  • Exception:又分为RuntimeException和其他Exception,由程序错误导致的异常属于RuntimeException,而程序无问题但外部(如I/O)导致的异常属于其他异常

在这里插入图片描述
如果出现RuntimeException,那一定是代码写错了,如常见的算术、空指针、数组溢出
在这里插入图片描述

应该处理或抛出何种异常?

将上述层次分为两大类:

  • unchecked异常:继承Error和RuntimeException类的异常
  • checked异常:除上面外的其他异常,所有checked异常都需提供异常处理器

一个方法应该处理或抛出其所有可能发生的checked异常,因为

  1. Error异常由Java内部产生,我们无法对其控制和修正
  2. RuntimeException可通过修改代码逻辑避免发生,而不应该关注出错后的处理

声明异常

方法不仅需要告诉调用者其返回的值,同时还需要告诉其可能发生的错误,通过throws关键字声明其可能发生的异常

class FileLoad {
    public void load() throws IOException {

    }
}

class ImageLoad extends FileLoad {
    public void load() {

    }
}

class TextLoad extends FileLoad {
    public void load() throws FileNotFoundException, EOFException {

    }
}

声明一个异常,并不代表只能抛出那个异常,也有可能抛出其子类异常(如上FileLoad的load()方法声明为IOException,但可能抛出其子类FileNotFoundException)

Tip:

  • 子类重写的方法可选择不声明异常,但不能声明比父类更通用的异常,即只能声明父类异常或其子类
  • 若父类方法没有声明checked异常,子类重写的方法也不能声明

抛出异常

方法经过声明后,内部即可能出现异常,需要在异常出现的地方使用throw关键字抛出异常

由于大多数异常是在运行时才能发生的,故下面模拟在读取一个10行的文件时,读到第5行因线程抢占导致线程中止,此时会抛出IO中断异常

class FileLoad {
    public void load() throws IOException {
        for (int i = 0; i < 10; i++) {
            if (i == 5) {
                throw new InterruptedIOException();
            }
        }
    }
}

自定义异常

若系统自带的异常类不够用,可通过继承Exception或其子类创建自己的异常类,通常需要两个构造方法,一个为默认构造方法,一个为带有详细描述信息的构造方法(通过toString打印)

class divideOneException extends ArithmeticException {
    public divideOneException() {
    }

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

class numDivide {
    void divide() throws divideOneException {
        int i = 2;
        int j = 1;
        if (j == 1) {
            throw new divideOneException();
        }
        i /= j;
    }
}

如上自定义了除1异常,当j==1时抛出异常(实际情况下j应由外部输入)

捕获异常

如果异常抛出后没有捕获,则会抛给上层调用者,一直往上抛,若无法解决,程序就会终止并打印错误信息,其包括异常类型和堆栈内容,如不想让程序终止,可通过try-catch关键字捕获并处理

int i = 2;
int j = 0;
int[] k = new int[1];
try {
    i /= j;
    k[2] = 1;
} catch (ArithmeticException | IndexOutOfBoundsException e) {
    e.printStackTrace();
}

Tips:

  • 将可能发生异常的代码放进try子句中
  • 可以捕获多个异常,e为final类型
  • 若try子句中的某行代码未抛出异常,跳过catch子句
  • 若try子句中的某行代码抛出了在catch子句中说明的异常类,将不再执行try子句中的后续代码,转而执行catch子句中的代码
  • 若try子句中的某行代码抛出了在catch子句中未说明的异常类,相当于未捕获异常,程序终止

异常链和异常包装

在catch中可以抛出一个异常,可在此改变异常的类型

class numDivide {
    void divide() throws IOException {
        int i = 2;
        int j = 0;
        int[] k = new int[1];
        try {
            i /= j;
        } catch (ArithmeticException e) {
            IOException throwable = new IOException("除0异常");
            throwable.initCause(e);
            throw throwable;
        }
    }
}

如上将ArithmeticException转换为IOException ,并通过Throwable抛出,当捕获到异常时,可通过下面的语句得到原始异常

ArithmeticException e = throwable.getCause();

finally

当代码抛出异常,程序终止,若当前方法获得了一些资源,则需在finally子句中进行释放。不管是否有异常被捕获,finally子句都会执行

private String load() {
        FileInputStream inputStream = null;
        BufferedReader bufferedReader = null;
        StringBuilder stringBuilder = new StringBuilder();
        try {
            inputStream = openFileInput("data");
            bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
            String line = "";
            while ((line = bufferedReader.readLine()) != null) {
                stringBuilder.append(line);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (bufferedReader != null) {
                try {
                    bufferedReader.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return stringBuilder.toString();
    }

如上是利用bufferedReader读取文件的代码,在finally子句中需要对bufferedReader流关闭,但关闭流的同时,也会出现异常,这个异常可能会覆盖掉原有的异常,所以需要对关闭操作可能产生的异常进行捕获

不要在finally子句中调用return

调用下面方法输入2,将会返回0,在try中的return返回前会调用finally子句,此时finally子句的return会覆盖掉原来的返回值。

int finallyReturn(int n) {
        try {
            int r = n * n;
            return r;
        } finally {
            if (n == 2) {
                return 0;
            }
        }
    }

try-with-resources

为了解决finally中可能产生异常导致的异常覆盖及代码繁琐,对于所有实现类AutoCloseable接口的类,可使用try-resources语句简写,将上面的load方法改为如下,就不必用finally中关闭流了

private String load() {

        StringBuilder stringBuilder = new StringBuilder();
        try (
                FileInputStream inputStream = openFileInput("data");
                BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream))
        ) {
            String line = "";
            while ((line = bufferedReader.readLine()) != null) {
                stringBuilder.append(line);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {

        }
        return stringBuilder.toString();
    }

Tips:

  • try-with-resources内部会捕获close方法抛出的异常,由addSuppressed方法增加到原来的异常
  • 可通过getSuppressed方法获取close方法抛出的异常列表

应该选择抛出还是捕获异常

通常,应该捕获那些知道如何处理的异常,而将那些不知道怎样处理的异常继续传递。如果要传递异常,需在方法首部添加throws标识此方法可能抛出异常。

但在前面曾说到若父类方法没有声明checked异常,子类覆盖后的方法也不能声明,故此时子类覆盖的方法必须捕获异常,不允许在子类throws抛出超过父类方法所列出的异常类的范围。

异常使用技巧

早抛出,当栈空时就应该抛出EmptyStackException,而不是返回null,因为接下来的调用仍会抛NullPointException

Object o = stack.pop();
o.toString();

晚捕获,传递异常可以让更高层调用对象知道可能会发生的错误

private String load() throws IOException {

        StringBuilder stringBuilder = new StringBuilder();
        try (
                FileInputStream inputStream = openFileInput("data");
                BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
        ) {
            String line = "";
            while ((line = bufferedReader.readLine()) != null) {
                stringBuilder.append(line);
            }
        }
        return stringBuilder.toString();
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值