Java异常(超详细!)

  • Post author:
  • Post category:java




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

  • 什么是异常:程序执行过程中的

    不正常

    情况。
  • 异常的作用:增强程序的


    健壮性



eg.

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

        // 此处运行也会创建一个:ArithmeticException类型的异常对象。
        System.out.println(100 / 0);
    }
}



2、java语言中异常是以什么形式存在的呢?

异常在java中以





的形式存在,每一个


异常类


都可以创建


异常对象



eg.

public class ExceptionTest02 {
    public static void main(String[] args) {
        // 通过“异常类”实例化“异常对象”
        NumberFormatException nfe = new NumberFormatException("数字格式化异常!");
        
        // java.lang.NumberFormatException: 数字格式化异常!
        System.out.println(nfe);
    }
}



3、异常继承结构图

在这里插入图片描述

  • Exception的直接子类:

    编译时异常

    (要求程序员在编写程序阶段必须预先对这些异常进行处理,如果不处理编译器报错,因此得名编译时异常。)。
  • RuntimeException:

    运行时异常

    。(在编写程序阶段程序员可以预先处理,也可以不管,都行。)



4、异常的分类

异常分为


编译时异常





运行时异常


所有异常都是在


运行阶段


发生的。因为只有程序运行阶段才可以

new

对象。

因为异常的发生就是


new异常对象




4.1编译时异常因为什么而得名?

因为编译时异常必须在编译(编写)阶段预先处理,如果不处理编译器报错,因此得名。



4.2 编译时异常和运行时异常的区别?

  • 编译时异常一般发生的概率


    比较高


  • 运行时异常一般发生的概率


    比较低


  • 编译时异常发生概率较高,需要在运行之前对其进行


    预处理


  • 运行时异常发生概率较低,

    没必要

    提前进行预处理。



4.3编译时异常和运行时异常别称

  • 编译时异常

  1. 受检

    异常:CheckedException

  2. 受控

    异常
  • 运行时异常

  1. 未受检

    异常:UnCheckedException

  2. 非受控

    异常


1、补充:

public class ExceptionTest03 {
    public static void main(String[] args) {
        System.out.println(100 / 0);

        // 这里的HelloWorld没有输出,没有执行。
        System.out.println("Hello World!");
    }
}

程序执行到

System.out.println(100 / 0);


此处发生了


ArithmeticException


异常,底层


new


了一个ArithmeticException

异常对象

,然后

抛出

了。

由于是


main方法


调用了100 / 0,所以这个异常ArithmeticException

抛给了main方法



main方法

没有处理

,将这个异常自动抛给了


JVM




JVM最终终止程序的执行

此时

System.out.println("Hello World!");

并不会执行。


注意:



ArithmeticException

继承

RuntimeException

,属于


运行时异常


。在编写程序阶段

不需要

对这种异常进行预先的处理。



eg.

public class ExceptionTest04 {
    public static void main(String[] args) {
        // main方法中调用doSome()方法
        // 因为doSome()方法声明位置上有:throws ClassNotFoundException
        // 我们在调用doSome()方法的时候必须对这种异常进行预先的处理。
        // 如果不处理,编译器就报错。
        //编译器报错信息: Unhandled exception: java.lang.ClassNotFoundException
        doSome();
    }

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

解决方法一、throws上报给方法调用者(推卸责任:调用者知道)

public class ExceptionTest04 {
    public static void main(String[] args) throws ClassNotFoundException {
        doSome();
    }
    public static void doSome() throws ClassNotFoundException{
        System.out.println("doSome!!!!");
    }
}

解决方法二、try…catch捕捉,处理(调用者是不知道)

public class ExceptionTest04 {
    public static void main(String[] args) {
        try {
            doSome();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
    
    public static void doSome() throws ClassNotFoundException{
        System.out.println("doSome!!!!");
    }
}



5、异常的处理方式



5.1 throws

在方法声明的位置上使用


throws



关键字

抛出,谁调用我这个方法,我就抛给谁。抛给


调用者


来处理。

这种处理异常的态度:

上报



5.2 try…catch

这个异常不会上报,自己把这个事儿处理了。

异常抛到此处为止,不再上抛了。


注意:

  • 只要异常没有捕捉,采用

    上报

    的方式,此方法的


    后续代码不会执行



  • try语句块

    中的某一行出现异常,该行


    后面的代码不会执行


  • try…catch

    捕捉异常之后

    ,后续代码可以执行。


eg.

private static void m1() throws FileNotFoundException {
    System.out.println("m1 begin");
    m2();
    // 以上代码出异常,这里是无法执行的。
    System.out.println("m1 over");
}
try {
    m1();
    // m1方法出异常,下面代码不执行。
    System.out.println("hello world!");//不执行
} catch (FileNotFoundException e){ 
	//异常处理
    System.out.println("出异常了!!");
    System.out.println(e); 
}
System.out.println("hello world"); //会执行


注意:

  • 异常发生之后,如果我选择了上抛,抛给了我的调用者,调用者需要对这个异常继续处理,那么调用者处理这个异常同样有两种处理方式。
  • 一般不建议在

    main方法

    上使用

    throws

    ,因为这个异常如果真正的发生了,一定会抛给JVM。JVM只有终止。
  • 一般main方法中的异常建议使用

    try…catch

    进行捕捉。


注意:

try {
    
} catch (ClassNotFoundException e) {
    e.printStackTrace();
}

这个分支中可以使用e引用,


e引用


保存的内存地址是那个

new

出来


异常对象的内存地址





6、在以后开发中,处理编译时异常,应该上报还是捕捉呢?

  • 如果希望

    调用者

    来处理,选择throws上报。
  • 其它情况使用捕捉的方式。



7、深入try…catch

  1. catch后面的小括号中的类型可以是


    具体的异常类型


    ,也可以是该异常类型的


    父类型


  2. catch可以写

    多个

    。建议catch的时候,

    精确的一个一个处理

    。这样有利于程序的调试。
  3. catch写多个的时候,

    从上到下

    ,必须遵守


    从小到大



eg.

try {
	FileInputStream fis = new FileInputStream("D:\\Download\\Javabean-addperson案例解析.docx");
} catch(FileNotFoundException e) {
	System.out.println("文件不存在!");
}

等同于

try {
	FileInputStream fis = new FileInputStream("D:\\Download\\Javabean-addperson案例解析.docx");
} catch(Exception e) {// 多态:Exception e = new FileNotFoundException();
	System.out.println("文件不存在!");
}
try {
    FileInputStream fis = new FileInputStream("D:\\Download\\Javabean-addperson案例解析.docx");
    fis.read();
} catch(IOException e){
    System.out.println("读文件报错了!");
} catch(FileNotFoundException e) {
    System.out.println("文件不存在!");
}
  1. JDK8的新特性:


    catch()

    异常间可以

    自小到大




    |


    分割


eg.

try {
    //创建输入流
    FileInputStream fis = new FileInputStream("D:\\Download\\Javabean-addperson案例解析.docx");
    // 进行数学运算
    System.out.println(100 / 0); // 这个异常是运行时异常,编写程序时可以处理,也可以不处理。
} catch(FileNotFoundException | ArithmeticException | NullPointerException e) {
    System.out.println("文件不存在?数学异常?空指针异常?都有可能!");
}



8、异常两个重要方法

方法名 作用
String

getMessage

()
返回异常的详细消息字符串
void

printStackTrace

()
追踪堆栈异常信息(采用异步线程)



9、finally字句

在finally子句中的代码是最后执行的,并且是


一定会执行


的,即使try语句块中的代码出现了异常。


finally

子句必须和

try

一起出现,不能单独编写。



9.1 finally语句通常使用在哪些情况下呢?

通常在finally语句块中完成


资源的释放/关闭



eg.

public class ExceptionTest10 {
    public static void main(String[] args) {
        FileInputStream fis = null; // 声明位置放到try外面。这样在finally中才能用。
        try {
            fis = new FileInputStream("D:\\Download\\Javabean-addperson案例解析.docx");
            String s = null;
            // 这里一定会出现空指针异常!
            s.toString();
            System.out.println("hello world!");

            // 流使用完需要关闭,因为流是占用资源的。
            // 即使以上程序出现异常,流也必须要关闭!
            // 放在这里有可能流关不了。
            //fis.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch(IOException e){
            e.printStackTrace();
        } catch(NullPointerException e) {
            e.printStackTrace();
        } finally {
            System.out.println("hello 浩克!");
            // 流的关闭放在这里比较保险。
            // finally中的代码是一定会执行的。
            // 即使try中出现了异常!
            if (fis != null) { // 避免空指针异常!
                try {
                    // close()方法有异常,采用捕捉的方式。
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}



9.2try和finally联用,没有catch


eg.

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

        // 这里不能写语句,因为这个代码是无法执行到的。
        //System.out.println("Hello World!");
    }
}


以下代码的执行顺序:

  1. 先执行try…
  2. 再执行finally…
  3. 最后执行 return (return语句只要执行方法必然结束。)


注意:

  • try不能单独使用。
  • try finally可以联合使用。
  • 放在finally语句块中的代码是一定会执行的



9.3 finally子句失效



System.exit(0);


只有这个可以治finally。

public class ExceptionTest12 {
    public static void main(String[] args) {
        try {
            System.out.println("try...");
            // 退出JVM
            System.exit(0); // 退出JVM之后,finally语句中的代码就不执行了!
        } finally {
            System.out.println("finally...");
        }
    }
}



9.4 finally面试题

public class ExceptionTest13 {
    public static void main(String[] args) {
        int result = m();
        System.out.println(result); //100
    }

    /*
    java语法规则(有一些规则是不能破坏的,一旦这么说了,就必须这么做!):
        java中有一条这样的规则:
            方法体中的代码必须遵循自上而下顺序依次逐行执行(亘古不变的语法!)
        java中海油一条语法规则:
            return语句一旦执行,整个方法必须结束(亘古不变的语法!)
     */
    public static int m(){
        int i = 100;
        try {
            // 这行代码出现在int i = 100;的下面,所以最终结果必须是返回100
            // return语句还必须保证是最后执行的。一旦执行,整个方法结束。
            return i;
        } finally {
            i++;
        }
    }
}


反编译之后的效果:

public static int m(){
    int i = 100;
    int j = i;
    i++;
    return j;
}



9.5 final finally finalize有什么区别?

  • final

    关键字
  1. final修饰的




    无法继承
  2. final修饰的

    方法


    无法覆盖
  3. final修饰的

    变量


    不能重新赋值

  • finally

    关键字
  1. finally 和

    try

    一起联合使用。
  2. finally语句块中的代码是

    必须执行

    的。
  • finalize

    标识符
  1. 是一个Object类中的方法名。
  2. 这个方法是由垃圾回收器

    GC

    负责调用的



10、自定义异常(开发中常用)



10.1前言

SUN提供的JDK内置的异常肯定是不够的用的。在实际的开发中,有很多业务,这些业务出现异常之后,JDK中都是没有的。和业务挂钩的。因此需要自定义异常。



10.2自定义异常步骤

  1. 第一步:编写一个类

    继承



    Exception


    或者


    RuntimeException


    .
  2. 第二步:提供两个


    构造方法


    ,一个

    无参数

    的,一个

    带有String参数

    的。


eg.

//栈操作异常:自定义异常!
public class StackOperationException extends Exception{ // 编译时异常!
    public MyStackOperationException(){

    }

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



11、方法覆盖,时遗留的问题

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

    方法覆盖
class Animal {
    public void doSome(){

    }

    public void doOther() throws Exception{

    }
}

class Cat extends Animal {

    // 编译正常。
    public void doSome() throws RuntimeException{

    }

    // 编译报错。
    /*public void doSome() throws Exception{

    }*/

    // 编译正常。
    /*public void doOther() {

    }*/

    // 编译正常。
    /*public void doOther() throws Exception{

    }*/

    // 编译正常。
    public void doOther() throws NullPointerException{

    }
}


注意:


一般不会这样考虑,方法覆盖复制一份,然后重写就好了。



12、总结异常中的关键字

  • 异常捕捉:


  1. try



  2. catch



  3. finally



  • throws


    在方法声明位置上使用,表示

    上报异常信息



    调用者



  • throw



    手动抛出异常


eg.

    public void pop() throws StackOperationException {
        if(index < 0){
            throw new MyStackOperationException("弹栈失败,栈已空!");//手动抛出异常
        }
    }

该方法index < 0时手动抛出异常,然后在方法里没有处理,上报给调用者,让调用者处理!



版权声明:本文为qq_44715943原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。