一 基本概念

1.什么是异常,java提供异常处理机制有什么用

以下程序执行过程中出现了不正常情况,而这种不正常情况叫做:异常

java把异常信息打印输出到控制台,供程序员参考,程序员可以根据异常信息对程序进行修改,让程序更加健壮。

public class ExceptionTest01 {
    public static void main(String[] args) {
        int a = 10;
        int b = 0;
        System.out.println(a / b);
        //实际上JVM在执行到此处的时候,会new异常对象:new ArithmeticException("/ by zero");
        //并且JVM将new的异常对象抛出,打印输出到控制台。
    }
}

运行结果:

Exception in thread "main" java.lang.ArithmeticException: / by zero
	at exception.text02.ExceptionTest01.main(ExceptionTest01.java:9)

这个信息我们称为异常信息,由JVM打印的。

2.java语言中异常以什么形式存在

1.异常在java中以类的形式存在,每一个异常类都可以创建异常对象。

public class ExceptionTest02 {
    public static void main(String[] args) {
        //通过异常类 实例化 异常对象
        
        NullPointerException n1 = new NullPointerException("空指针异常");
        System.out.println(n1);
        
        NumberFormatException n2 = new NumberFormatException("数字格式化异常");
        System.out.println(n2);
    }
}

运行结果:

java.lang.NullPointerException: 空指针异常
java.lang.NumberFormatException: 数字格式化异常

2.异常对应的现实生活(帮助理解)

火灾(异常类):

2008年8月7日,小明家着火了(异常对象)

2008年8月8日,小刚家着火了(异常对象)

2008年8月9日,小红家着火了(异常对象)

类:模板

对象:实际存在的个体

二 异常处理机制

1.异常在java中以类和对象的形式存在。那么异常的继承结果是怎样的呢?

Object

Object下有Throwable(可抛出的)

Throwable下有两个分支:Error(不可处理,直接退出JVM)和Exception(可处理的)

Exception下有两个分支:

Exception的直接子类:编译时异常(要求程序员在编写程序阶段必须对这些异常进行预处理)

RuntimeException:运行时异常。(在编写程序阶段可以预先处理,也可以不管)

java 异常如何获调用栈详细信息 java异常信息怎么看_JVM

(图片来自网络)

2.编译时异常(受检异常)和运行时异常

  • 都是发生在运行阶段,编译阶段异常是不会发生的,因为异常的发生就是new对象,只有程序运行阶段才可以创建对象。
  • 编译异常一般发生的概率比较高(对于发生概率较高的异常,需在运行前对其进行预处理)
    举个例子:
    你看到外面下雨了,倾盆大雨。
    你出门前会预料到:如果不打伞,我可能生病(异常)。
    而且这个异常发生的概率很高,所以我会在出门前拿一把伞。
    “拿一把伞”就是对“生病异常”发生之前的一种处理。
  • 运行时异常一般发生的概率比较低
    举个例子:
    小明走在大街上,可能被天上飞机的轮子砸到。
    被天上飞机的轮子砸到是一种异常。
    但这个异常发生的概率极低。
    出门前没必要对这种发生概率极低的异常进行预处理。
    如果处理这种异常,你将活得很累。

3. java语言中对异常的处理包括两种方式:

第一种方式:在方法声明的位置上,使用throws关键字,抛给上一级。

第二种方式:使用try···catch语句进行异常的捕捉。

举个例子:

我是某集团的一个销售员,因为我的失误,导致公司损失了1000元。‘

损失1000元可以看作是一个异常发生了。我有两种处理方式:

第一种:我把这件事告诉我的领导【异常上抛】

第二种:我自己掏腰包把这个钱补上。【异常的捕捉】

注意:异常发生之后,如果选择上抛,抛给了我的调用者,调用者需要对这个异常继续处理,同样有两种方式。如果一直上抛,最终抛给了main方法,main方法继续向上抛,抛给了调用者JVM,JVM知道这个异常发生,终止java程序的执行。

package exception.text02;

import java.util.Arrays;

public class ExceptionTest03 {
    public static void main(String[] args) {
        /*
           程序执行到此处发生了ArithmeticException异常,
           底层new了一个ArithmeticException异常对象,
           然后抛出了,由于是main方法调用了100 / 0,
           所以这个异常ArithmeticException抛给了main方法,
           main方法没有处理,将这个异常抛给了JVM。
           JVM终止程序的执行。
         */
        System.out.println(100 / 0);

        System.out.println("Hello World!");//没有执行
    }
}

运行结果:

Exception in thread "main" java.lang.ArithmeticException: / by zero
	at exception.text02.ExceptionTest03.main(ExceptionTest03.java:15)

第一种方式:在方法声明的位置上,使用throws关键字,抛给上一级。

public class ExceptionTest04 {
    public static void main(String[] args) {
        /*
        main方法中调用了doSome()方法,
        而doSome()方法声明位置上有throws ClassNotFoundException
        我们调用doSome()方法的时候必须对这种异常进行预先的处理
        如果不处理,编译器报错
         */
        doSome();
    }

    /**
     * doSome()方法声明位置上有throws ClassNotFoundException
     * 这个代码表示doSome()方法在执行过程中,有可能出现ClassNotFoundException异常
     * 叫做类没找到异常。这个异常的直接父类是:Exception,所以ClassNotFoundException属于编译时异常
     * @throws ClassNotFoundException
     */
    private static void doSome() throws ClassNotFoundException{
        System.out.println("doSome!!!");
    }
}

"运行"结果:

java: 未报告的异常错误java.lang.ClassNotFoundException; 必须对其进行捕获或声明以便抛出
public class ExceptionTest05 {
    //第一种处理方式:在方法声明的位置上使用:throws,来完成异常的继续上抛,抛给调用者。
    //上抛相当于推卸责任。(继续把异常传递给调用者)
    /*
     public static void main(String[] args) throws ClassNotFoundException {
         doSome();
     }
     */
    //第二种处理方式:try ··· catch进行捕捉。
    //捕捉相当于把异常拦下来了,异常得到真正的解决(调用者是不知道的)
    public static void main(String[] args) {
        try {
            doSome();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
    private static void doSome() throws ClassNotFoundException{
        System.out.println("doSome!!!");
    }
}

运行结果:

doSome!!!
import java.io.FileInputStream;
import java.io.FileNotFoundException;

public class ExceptionTest06 {
    /*
    一般不建议在main方法上使用throws,因为这个异常如果真的发生了,一定会抛给JVM,JVM只有终止。
    main方法中的异常建议使用try···catch进行捕捉,不要继续上抛。
     */
    public static void main(String[] args)  {
        System.out.println("main begin");
        //try尝试
        try {
            m1();
            //以上代码出现问题,直接进入catch语块中执行。
        } catch (FileNotFoundException e) {
            //这个分支可以使用e引用,e引用保存的内存地址是那个new出来异常对象的地址
            System.out.println("文件不存在");
        }
        System.out.println("main over");
    }
    
    //throws后面可以写多个异常,用逗号隔开
    private static void m1() throws FileNotFoundException {
        System.out.println("m1 begin");
        m2();
        System.out.println("m1 over");
    }

    private static void m2() throws FileNotFoundException {
        System.out.println("m2 begin");
        m3();
        System.out.println("m2 over");
    }

    private static void m3() throws FileNotFoundException {
        /*
        这里我们调用了一个构造方法:FileInputStream(String name)
        这个构造方法的声明位置上有:throws FileNotFoundException
        通过类的继承结构可以看到:FileNotFoundException的父类是IOException,IOException的父类是Exception
         */
        new FileInputStream("D:\\123.txt");
    }
}

运行结果:

未出现异常:

main begin
m1 begin
m2 begin
m2 over
m1 over
main over

出现异常:

main begin
m1 begin
m2 begin
文件不存在
main over

注意:只要异常没有捕捉,采用上报的方式,此方法的后续代码不会执行。

另外try语句中的某一行出现异常,该行后面的代码不会执行。

try···catch捕捉异常后,后续代码可以执行。

第二种方式:使用try···catch语句进行异常的捕捉。

  1. catch后面的小括号中可以是具体的异常类型,也可以是异常型的父类型。
  2. catch可以写多个。建议catch的时候,精确的一个一个处理。这样有利于程序的调试。
  3. catch写多个的时候,从上到下,必须遵守从小到大。
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;

public class ExceptionTest07 {
    public static void main(String[] args) {
        try{
            FileInputStream fileInputStream = new FileInputStream("D:\\123.txt");
        }  catch (FileNotFoundException e) {
            System.out.println("文件不存在!");
        }
        System.out.println("Hello World!");

        try{
            FileInputStream fileInputStream = new FileInputStream("D:\\123.txt");
        }  catch (IOException e) {
            System.out.println("文件不存在!");
        }
    }
}

运行结果:

未出现异常:

Hello World!

出现异常:

文件不存在!
Hello World!
文件不存在!
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;

public class ExceptionTest07 {
    public static void main(String[] args) {
        try{
            FileInputStream fis = new FileInputStream("D:\\123.txt");
            fis.read();//仅举例
        } catch (FileNotFoundException e){
            System.out.println("文件不存在");
        }  catch (IOException e){
            System.out.println("读文件报错了");
        }
    }
}
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;

public class ExceptionTest07 {
    public static void main(String[] args) {
        try{
           // 创建输入流
            FileInputStream fis = new FileInputStream("D:\\123.txt");
            System.out.println(100 / 10);//运行时异常
        } catch (FileNotFoundException | ArithmeticException |NullPointerException e){
            //JDK8
            System.out.println("文件不存在?数学异常?空指针异常?都有可能!");
        }
    }
}

运行结果:

未出现异常:

10

出现异常:

文件不存在?数学异常?空指针异常?都有可能!

注:在以后的开发中,处理编译时异常,应该上报还是捕捉,怎么选?

如果希望调用者来处理,选择throws上报。

其它情况使用捕捉的方式。

4.异常对象的两个重要方法

  1. 获取异常简单的描述信息
    String msg = exception.getMessage();
  2. 打印异常追踪的堆栈信息
    exception.printStackTrace();
public class ExceptionTest08 {
    public static void main(String[] args) {
        //这里只是为了测试getMessage()方法和printStackTrace()方法。
        //这里只是new了异常对象,但没有将对象抛出,JVM会认为这是一个普通的java对象。
        NullPointerException e = new NullPointerException("空指针异常");
        //获取异常简单描述信息:这个信息实际上就是构造方法上面的String参数。
        String msg = e.getMessage();
        System.out.println(msg);
        //打印异常堆栈信息
        //java后台打印异常堆栈追踪信息的时候,采用了异步线程的方式打印
        e.printStackTrace();
    }
}

运行结果:

空指针异常
java.lang.NullPointerException: 空指针异常
	at exception.text02.ExceptionTest08.main(ExceptionTest08.java:7)

我们查看异常的追踪信息,应怎么看,可以快速调试程序:

异常的追踪信息,从上往下一行一行看。

但需要注意的是:SUN写的代码就不用看了(看包名就知道是自己的还是SUN的)

主要的问题是出现在自己编写的代码上。

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

public class ExceptionTest09 {
    public static void main(String[] args) {
        try {
            m1();
        } catch (FileNotFoundException e) {
            e.printStackTrace();//实际开发建议使用这个
            String msg = e.getMessage();//获取异常简单的描述信息
            System.out.println(msg);
        }
    }

    private static void m1() throws FileNotFoundException {
        m2();
    }

    private static void m2() throws FileNotFoundException {
        m3();
    }

    private static void m3() throws FileNotFoundException {
        new FileInputStream("D:\\23.txt");//该文件不存在
    }
}

运行结果:

java.io.FileNotFoundException: D:\23.txt (系统找不到指定的文件。)
	at java.base/java.io.FileInputStream.open0(Native Method)
	at java.base/java.io.FileInputStream.open(FileInputStream.java:211)
	at java.base/java.io.FileInputStream.<init>(FileInputStream.java:153)
	at java.base/java.io.FileInputStream.<init>(FileInputStream.java:108)
	at exception.text02.ExceptionTest09.m3(ExceptionTest09.java:26)
	at exception.text02.ExceptionTest09.m2(ExceptionTest09.java:22)
	at exception.text02.ExceptionTest09.m1(ExceptionTest09.java:18)
	at exception.text02.ExceptionTest09.main(ExceptionTest09.java:9)
D:\23.txt (系统找不到指定的文件。)

5. finally

关于try···catch中的finally子句:

  1. 在finally中的代码是最后执行的,并且一定执行,即使try语块中的代码出现了异常。
  2. finally语句通常使用在哪些情况呢?
    通常在finally语块中完成资源的释放/关闭,
    因为finally中的代码比较有保障。
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;

public class ExceptionTest10 {
    public static void main(String[] args) {
        FileInputStream fis = null;//声明放到try外面,这样在finally中才能使用。
        try{
            //流使用完需要关闭
            fis = new FileInputStream("D:\\23.txt");//该文件不存在
            
            String s = null;//这里一定会出现空指针异常!
            s.toString();
            System.out.println("Hello World!");
            //fis.close();//放在这里可能关闭不了
        } catch (FileNotFoundException e){
            e.printStackTrace();
        }  catch (NullPointerException e){
            e.printStackTrace();
        } finally {

            //流的关闭放在这里比较保险,
            //finally中的代码一定会执行
            //即使try中出现了异常
            if(fis != null){//避免空指针异常
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("Hello Over");
        }
    }
}

运行结果:

java.io.FileNotFoundException: D:\23.txt (系统找不到指定的文件。)
	at java.base/java.io.FileInputStream.open0(Native Method)
	at java.base/java.io.FileInputStream.open(FileInputStream.java:211)
	at java.base/java.io.FileInputStream.<init>(FileInputStream.java:153)
	at java.base/java.io.FileInputStream.<init>(FileInputStream.java:108)
	at exception.text02.ExceptionTest10.main(ExceptionTest10.java:11)
Hello Over
  1. try和finally,没有catch可以吗?可以。
    try不能单独使用,try finally可以联合使用。
    以下代码的执行顺序:
    先执行try···
    再执行finally···
    最后执行 return(return语句只要执行方法必然结束)
public class ExceptionTest11 {
    public static void main(String[] args) {
        try{
            System.out.println("try ~");
             return;
        } finally{
            System.out.println("finally ~");

        }
    }
}

运行结果:

try ~
finally ~

若在try中退出JVM,finally语句中的代码就不会执行

public class ExceptionTest12 {
    public static void main(String[] args) {
        try{
            System.out.println("try ~");
            System.exit(0);
        }finally {
            System.out.println("finally ~");
        }
    }
}

运行结果:

try ~

java语法规则(有一些规则是不能破坏的,一旦这么说了,就必须这么做!)

java中有这样两条规则:

  1. 方法体中的代码必须遵循自上而下顺序依次逐行执行
  2. return语句一旦执行,整个方法必须结束。
public class ExceptionTest13 {
    public static void main(String[] args) {
        int result = m();
        System.out.println("result = " + result);
    }

    private static int m() {
        int i = 100;
        try{
            //这行代码在int i = 100;的下面,所以最终返回的结果必须是100
            return i;
        }finally {
            i++;
            System.out.println("i = " + i);
        }
    }
}
/*
反编译之后的结果(省略了一部分)
    public static int m(){
    int i = 100;
    int j = i;
    i++;
    return j;
}
 */

运行结果:

i = 101
result = 100

final finally finalize的区别:

  1. final 关键字
    final修饰的类无法继承
    final修饰的方法无法覆盖
    final修饰的变量不能重新赋值
  2. finally 关键字
    和try一起联合使用。
    finally语句块中的代码是必须执行的。
  3. finalize 标识符
    是一个Object类的方法名。
    这个方法由垃圾回收器GC负责调用。

6.自定义异常

  1. SUN提供的JDK内置的异常是不够用的。在实际开发中,有很多业务,
    这些业务出现异常之后,JDK中是没有的。这些异常需由程序员自定义。
  2. java中怎么自定义异常呢?
    两步:
    第一步:编写一个类继承Exception或者RuntimeException。
    第二步:提供两个构造方法,一个无参数的,一个带有String参数的。
package exception.text02;

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

    public MyException(String message) {
        super(message);
    }
}
package exception.text02;

public class MyExceptionTest15 {
    public static void main(String[] args) {
        //创建异常对象(只new了异常对象,并没有手动抛出)
        MyException e = new MyException("用户名不能空!");
        e.printStackTrace();
        String msg = e.getMessage();
        System.out.println(msg);
    }
}

运行结果:

exception.text02.MyException: 用户名不能空!
	at exception.text02.MyExceptionTest15.main(MyExceptionTest15.java:6)
用户名不能空!

7.方法重写中的异常

重写之后的方法不能比之前的方法抛出更多(更广泛)的异常,可以更少。

8.使用throw抛出异常

throw总是出现在函数体中,用来抛出一个Throwable类型的异常。程序会在throw语句后立即终止,它后面的语句执行不到,然后在包含它的所有try块中(可能在上层调用函数中)从里向外寻找含有与其匹配的catch子句的try块。

package exception.text01;

class MyException extends Exception {
    public MyException() {}
    public MyException(String msg){super(msg);}
}
public class FullConstructors {
    public static void f() throws MyException{
        System.out.println("Throw MyException from f()");
        throw new MyException();
    }
    public static void g() throws MyException{
        System.out.println("Throw MyException from g()");
        throw new MyException("Originated in g()");
    }

    public static void main(String[] args) {
        try {
            f();
        }catch (MyException e){
            e.printStackTrace(System.out);
        }
        try {
            g();
        }catch (MyException e){
            e.printStackTrace(System.out);
        }
    }
}

运行结果:

Throw MyException from f()
exception.text01.MyException
	at exception.text01.FullConstructors.f(FullConstructors.java:10)
	at exception.text01.FullConstructors.main(FullConstructors.java:19)
Throw MyException from g()
exception.text01.MyException: Originated in g()
	at exception.text01.FullConstructors.g(FullConstructors.java:14)
	at exception.text01.FullConstructors.main(FullConstructors.java:24)