Java学习之旅-异常

307 阅读6分钟

Java学习之旅-异常

概念:Java当中有异常处理机制,来处理程序运行过程中可能发生的意外,很好地提高了程序的健壮性

Throwable及其子类

Throwable下有Error和Exception两个子类:

  1. Error:有VirtualMachineError(虚拟机错误)、OutOfMemoryError(内存溢出)、ThreadDeath(线程死锁)
  2. Exception:有检查异常和非检查异常两个部分:
    1. 其中检查异常有IOException(IO异常)、SQLException(SQL异常),当该类异常被抛出时, 编译器会强制要求处理
    2. 非检查异常也就是RuntimeException又有四种,当该类异常被抛出时, 编译器不会强制要求处理 (如果出现RuntimeException,那么一定是你的问题,因此应该在使用变量之前通过检测它是否为NULL来避免NullPointerException;在使用数组前检测数组下标是否越界):
      1. NullPointerException(空指针异常)
      2. ArrayIndexOutOfBoundsException(数组下标越界异常)
      3. ArithmeticException(算数异常)
      4. ClassCastException(类型转换异常)

try-catch-finally

这是一个关键字的组合,try{}代码块内存放可能出现异常的代码;如果出现异常对象,并且和catch的异常类型匹配,则执行catch{}内存放的代码块,可以有多重catch;finally{}里存放的代码是无论是否有异常对象被抛出,也会执行。

如果想要不执行finally里的语句呢?

这里有两个办法:System.exit(1);和return,但是return比较特殊,不论return在哪个位置,都会把finally执行完再return。

要点

  1. 最后的一个catch最好使用Exception这个父类对象,避免自己遗漏的异常类型没法被捕获
  2. 在处理异常的时候,根据具体需要的业务逻辑来决定
  3. finally语句块中尽量用来释放占用的资源,从而显得逻辑性更强

throws和throw

通过这两个关键字来抛出异常,给外部捕获,从而进行处理。可以做到“谁调用这个方法,谁来处理”,而不是自己调用,自己发现异常,自己处理。

public static void main(String args) {
        try {
            int result = test();
            System.out.println("one 和 twe 的商是:" + result);
        } catch(InputMismatchException e){
            System.out.println("输入格式错误");
        } catch(ArithmeticException h){
            System.out.println("这又是什么异常");
        }catch (Exception a ){
            System.out.println("什么异常?");
        }
    }    
/**
 * 这里会把要写的异常注解出来,这样在鼠标停在需要处理异常的方法上的时候,可以看到提示
 * @return
 * @throws Exception
 */
public static int test()throws Exception {
        Scanner input = new Scanner(System.in);
        System.out.println("------START-------");
            System.out.print("输入第一个数字one:");
            int one =input.nextInt();
            System.out.print("输入第二个数字two:");
            int two = input.nextInt();
            System.out.println("-------运算结束------");
            return one/two;
    }

自抛自接也可:

public static int test()throws Exception {
        try {Scanner input = new Scanner(System.in);
        System.out.println("------START-------");
            System.out.print("输入第一个数字one:");
            int one =input.nextInt();
            System.out.print("输入第二个数字two:");
            int two = input.nextInt();
            System.out.println("-------运算结束------");
            return one/two;
        }catch(InputMismatchException e){
            System.out.println("输入格式错误");
        } catch(ArithmeticException h){
            System.out.println("这又是什么异常");
        }catch (Exception a ){
            System.out.println("什么异常?");
        }
    }

编译器未提醒程序员来处理非检查异常,有什么别的办法吗?

/**
 * 这里会把要写的异常注解出来,这样在鼠标停在需要处理异常的方法上的时候,可以看到提示
 * @return
 * @throws Exception
 */

以上的的注解形式,在IDEA中会呈现特殊颜色,会自动检测你的方法中是否有需要处理的异常。从而可以让程序员的鼠标放在方法调用的地方稍微久一点,就可以看到这个方法有什么异常需要处理,是一种提醒程序员编程不出现遗漏的方法。

子类重写父类方法时,如果父类方法中有声明特定的检查型异常该怎么办呢?

  1. 首先,子类的重写方法不可以声明父类方法中异常对象的父类,比如父类中声明了RuntimeException,子类的重写方法就可以用ArrayIndexOutOfBoundsException等(只可以写更特定的异常或者跑不出任何异常)。
  2. 其次,如果父类的方法没有抛出任何异常,子类方法也不可以抛出任何异常。

创建自己的异常类

通过创建自己的异常类,来描述自己特定的业务需要。定义一个派生于Exception的类,或者派生于Exception的某个子类。而这个自定义的类中会有两个构造器,一个是默认的,另一个是包含详细信息的构造器(超类Throwable的toString方法会返回一个字符串,包含这个详细信息),因为Exception也是Throwable的子类。

异常链

可以在catch中继续抛出新的异常,来改变异常的类型,有时候不一定想知道故障异常的具体细节,只是想知道是否出了故障。假设之前的代码中,抛出的是ArithmeticException异常,我们来对他做一个包装,而不是直接抛出另一种异常对象,因为不包装直接抛出会丢失原本的异常信息。如果我们如下图一样,包装后再抛出,就可以再利用 Throwable original = caughException.getCause(); 这一句来获取原始异常。

public static void main(String args) {
    try {
        int result = test();
        System.out.println("one 和 twe 的商是:" + result);
    } catch (ArithmeticException a ){
        var a=new SerletException("database error");
        e.initCause(original);
        throw e;
    }
}

或者,只是记录一个异常,再重新抛出:

try {
       ....
    } catch (Exception a ){
        logger.log(level,maeeage,e)
        throw e;
    }

var

上面的案例中出现了var这个关键字,做一个记录:var是Java10的新特性,它可以暂时代替各种数据类型的关键字:

  1. 只能用在局部变量
  2. 声明时必须初始化
  3. 不能作方法的参数

在编译时,var会被代替成所赋值的类型。

如果try语句中抛出异常被catch接到,catch又抛出一个异常给这个方法的调用者呢?

public static void main(String[] args) throws Exception {
        try {
            int result = test();
            System.out.println("one 和 twe 的商是:" + result);
        } catch (ArithmeticException e){
            System.out.println("出现了算数异常");
            throw new Exception("新的错误");
            System.out.println("又出现了算数异常");//这一句会报错
        } finally {
            System.out.println("新的错误结束");
        }
    }

很显然,抛出了新错误后,catch后面的语句就不会继续执行了。

try-with-Resources

似乎现在try-with-Resources语句要比finally子句更常用,它的特点是可以省略finally,在出现异常和退出时自动调用System.in.closa()方法,从而达到使用的finally块的效果。这里的try with Resources并不是三个关键字的意思,而是带有资源的try语句:

try(Resource res=...)
{
    work with res
}

还有一个有趣的地方,如果try抛出一个异常的同时,close方法也来一个异常,好家伙,这就有趣了。这时候,try-with-Resources会自动抑制close的异常,而try的异常会被重新抛出。也可以通过调用getSuppressed方法来调出close的异常。

总之,如果需要使用并关闭资源,用try-with-Resources