Java异常

1.什么是异常?

异常很好理解,就是程序运行中出现的不正常的情况。

我们知道除数是不能为0的,现在来模拟一个除数为0的异常:

package com.dh.exception;

public class Exception01 {

    public static void main(String[] args) {

        int i = 1/0;
        System.out.println(i);
    }
}

结果:

Exception in thread "main" java.lang.ArithmeticException: / by zero
	at com.dh.exception.Exception01.main(Exception01.java:7)

Process finished with exit code 1

上述代码在编译时是不会报错的,但是在运行时就会报一个java.lang.ArithmeticException: / by zero,并且可以看到,程序并没有输出i,所以发生异常的语句之后的代码都不会被执行。

Java提供了异常处理机制,会将异常信息输出到控制台,方便写代码的人去查找错误原因,更高效的调试处理,不仅输出了是什么异常,并且还告诉了你导致异常出现的代码是在第几行,非常方便程序员去找错误,使程序更安全、更健壮。

试想一下,要是没有这些输出信息。。。

此刻可以大喊一声,Java牛逼!

2.异常的分类

我们尝试着能不能在api文档中查一下ArithmeticException是个什么东西:

通过查api文档,我们可以看到ArithmeticException是个类,所以,Java语言中,异常是以异常类的形式存在,在发生异常的时候,虚拟机会new一个异常对象,并且将改异常对象的信息打印到控制台上。

我们也可以看到,ArithmeticException的鼻祖的确是Object,而Throwable直接继承了Object,ArithmeticException间接的继承了Throwable。我们再去api文档中查看一下Throwable是什么?

文档解释为:Throwable的直接子类有Error和Exception。(因为文档翻译的不太好,故不在此放文档解释的截图)

刚刚的是个异常信息,并且在Exception下还有个子类为RuntimeException,但是错误信息我们之前也见过,在递归调用的时候,如果自己不停的调用自己,就会出现StackOverFlowError。

所以,Java的异常大概可以分为如下:

Throwable就是可抛出的意思,Error和Exception直接继承Throwable了,不难得出,Error和Exception都可抛出。

但是,错误是不能处理的,所有的Error,只要发生了,就会终止程序的运行,退出JVM。

异常是可处理的。异常又分为运行时异常和编译时异常:Exception中所有直接子类都为编译时异常,RuntimeException下的所有子类都为运行时异常。

  • 编译时异常:也称为受控性异常。因为在编写代码时,就会直接红色波浪线报错,这种异常是有程序员造成的,发生的概率一般比较高(这...?),且必须进行预处理,否则会一直红色波浪线,虽然你也可以强制运行,但纯属浪费时间而已并没有什么意义。

  • 运行时异常:也称为非受控时异常。在编写代码的时候,没有红色波浪线报错提示,但是一运行就会报异常,如文中开头就提到的除数为0,这是由虚拟机造成的,所以运行时异常一般发生的概率比较低,可以预处理也可以不必预处理。预处理的话像上述例子,就是先判断除数是否为0,不为0时再进行除法运算,否则给出除数不能为0的提示。

注意:

运行时异常和编译时异常都是在程序运行过程中发生的。道理很简单,只有在程序运行的时候才可以new异常类的对象,不要因为编译时异常这个名字就觉得编译时异常是在编译时发生的。

3.异常处理的方式

如果你在工作的时候,不小心出现了一个失误,比如说让公司损失了1000块钱,那么现在你有两种解决方式,第一种是你自己垫钱补上,第二种是上报你的领导。

异常就相当于上述的失误,而异常处理的方式,原理也是类似的,一般有两种:

  • 使用try...catch,在catch中抓住异常。

  • 在方法声明的最后,使用throws+异常类,抛出该方法的调用者,调用者也可以使用这两种方式处理,直到最后的main()。

    但是这种方式不一定能解决掉异常,如果你一个不小心,让公司损失了1000w,那么此时上报你的直接领导,解决不了,再上报给领导的直接领导...一直到公司的boss,他也解决不了,那就只能大家一起完蛋了。

    我们知道所有的方法都是在main中直接或间接调用的,所以最后一个方法的调用一定是main,所以如果main也处理不了,就会抛给JVM,JVM就会结束程序的运行,退出JVM了。

(1)运行时异常的处理

1.try...catch:

try中写可能会出现异常的代码

catch紧跟的的()中接收异常对象,该异常对象必须是实际发生异常的对象或者父类,不能是无关的异常对象和子类对象接收,写代码出现异常时的操作,所以catch只有出现异常时才执行。

catch可多个,大的异常要放在下面,(不然大的放上面,下面那个catch就没有意义了),否则会报错

或者单个,在()中使用|,如(AclNotFoundException | ActivationException e)

package com.dh.exception;

public class Exception01 {

    public static void main(String[] args) {

        try {
            int i = 1/0;
            System.out.println(i);
        }catch (ArithmeticException e){ //接收程序运行过程中new的异常对象
            System.out.println("除数不能为0");
        }
    }
}

此时的结果为:程序输出

除数不能为0

将上述式子改为1时,此时没有发生异常,程序就不会执行catch子句,就不会输出除数不能为0这句话 。

2.throws:

package com.dh.exception;

public class Exception02 {

    //在方法的声明的最后加上throws 异常类的名称
    public static void main(String[] args) throws ArithmeticException{
        
        int i = 1/0;
        System.out.println(i);
    }
}

结果:

Exception in thread "main" java.lang.ArithmeticException: / by zero
	at com.dh.exception.Exception02.main(Exception02.java:7)

Process finished with exit code 1

可以看到此时仍然抛出了异常,这是因为:在main方法中发生了异常,却没有处理异常,此时将异常对象向上抛抛给了JVM,JVM就结束程序的运行,输出异常信息,退出JVM。

如果程序没有发生错误,就没有异常对象,main也不需要向上抛,程序就会继续运行,正常结束。

(2)编译时异常处理

我们先模拟一个编译时异常:

package com.dh.exception;

public class Exception03 {

    //查api文档,找一个Exception的直接子类:CloneNotSupportedException
    public static void show() throws CloneNotSupportedException{
        System.out.println("show");
    }

    public static void main(String[] args) {
        show();//红色波浪线报错
    }
}

此时程序会报错,这是因为show方法中throws的CloneNotSupportedException为编译时异常,而它的调用者没有预处理这个异常,所以会报错,此时提示也给出了两种解决方法。

1.try...catch:

2.throws:

这两种方式程序都不会报错了,但是throws是不能解决问题的,我们还是得使用try...catch来解决。

4.异常对象常用方法

自动生成的try...catch中,有一句e.printStackTrace();

printStackTrace()是异常对象的方法,打印异常输出的堆栈信息;(最常用!也推荐使用)

常用的方法还有:getMessage(),获取对象的描述信息。

5.finally子句

finally{}可直接和try使用,也可以和try...catch一起使用。

finally子句中的代码,无论是否发生异常,都一定会执行!!(充钱了吧?)

(除非来了,除非System.exit(0)),这是会退出JVM,它就没机会执行了!

finally语句常用于关闭一些东西,因为如文件流、数据库连接等不关闭会很消耗资源,所以在使用完之后必须关闭。

例:

FileInputStream用于读取诸如图像数据的原始字节流,我们点进去源码可以看到:

public FileInputStream(String name) throws FileNotFoundException {
    this(name != null ? new File(name) : null);
}

其构造方法抛出了FileNotFoundException,一直点击异常,可以发现其间接的继承了Exception子类,所以是编译时异常,必须要预先处理,否则会报错,在main中我们就最好try...catch处理了。

package com.dh.exception;

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

public class Exception04 {

    public static void main(String[] args) {

        FileInputStream file = null;
        try {
            //输入错误路径引发异常
            file = new FileInputStream("aaa");
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            System.out.println("我执行啦!");
        }
    }
}

结果:

可以看到,输入程序出现了异常,但是finally中的语句还是执行了。

也可以去掉catch,但是如果去掉了之后,就没有异常信息输出了,程序出现了异常你都不知道,还会傻乎乎的以为一切ok,所以最好不要去掉catch,当出现异常时,它对我们特别有帮助!

相关面试题(坑!):

package com.dh.exception;

public class Exception05 {

    public static void main(String[] args) {
        try{
            System.out.println("try......");
            return;
        }finally {
            System.out.println("catch......");
        }
    }
}

结果:

try......
catch......

我们之前说过,遇到return会直接返回,不会再运行下面的代码了,但是在这里finally是比retrn先执行的。

再来:

package com.dh.exception;

public class Exception05 {

    public static void main(String[] args) {
        
        System.out.println(show());
    }

    public static int show(){
        int i = 100;
        try {
            return i;
        }finally {
            i++;
        }
    }
}

看看结果:

100

结果居然还是100!并不是101。

这是以为Java一开始定下了一些语法,不可变的,否则就是打脸了:

  • 方法体{}中的代码必须按照自上而下的顺序逐行执行。

  • return语句一旦执行,方法必须结束。

那程序到底是怎么执行的呢?我们可以去看看.class文件:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package com.dh.exception;

public class Exception05 {
    public Exception05() {
    }

    public static void main(String[] args) {
        System.out.println(show());
    }

    public static int show() {
        byte i = 100;

        byte var1;
        try {
            var1 = i;
        } finally {
            int var5 = i + 1;
        }

        return var1;
    }
}

原来是先把i赋值给了一个临时变量,如何执行i++,return语句的确是最后执行的,并且返回的是临时变量。

6.final、finally、finalize的区别

  • final是关键字,状态修饰符。修饰的变量为常量,修饰的方法不能被重写,修饰的类不能被继承;

  • finally也是一个关键字,是try的一个子句,一定会执行里面的代码,除非系统退出;

  • finalize是一个标识符,是Object中的一个方法,finalize(),是一个有关垃圾回收的方法。

posted @ 2021-01-25 20:37  deng-hui  阅读(194)  评论(0编辑  收藏  举报