Java异常处理机制

三种类型的异常

检查性异常

最具代表的检查性异常是用户错误或问题引起的异常,这是程序员无法预见的。

例如要打开一个不存在的文件时,一个异常就发生了,这些异常在编译时不能被简单的忽略

运行时异常

运行时异常是可能被程序员避免的异常,与检查性异常相反,运行时异常可以在编译时被忽略

例如递归调用,A方法中调用B方法,B方法中调用A方法

错误

错误不是异常,而是脱离程序员控制的问题,错误在代码中通常被忽略

例如当栈溢出时,一个错误就发生了,它们在编译也检查不到

异常类体系结构

错误:Error类对象由Java虚拟机生成并抛出,大多数错误与代码编写者所执行的操作无关。错误不能被程序员通过代码处理,Error很少出现。还有发生在虚拟机试图执行应用时如类定义错误(NoClassDefFoundError)、链接错误(LinkageError)。这些错误是不可查的,因为它们在应用程序的控制和处理能力之外,而且绝大多数是程序运行时不允许出现的状况。

异常:Exception以及他的子类,代表程序运行时发送的各种不期望发生的事件。可以被Java异常处理机制使用,是异常处理的核心,这些异常是有程序逻辑错误引起的,程序应该从逻辑角度尽可能避免这类异常的发生。

在Exception分支中有一个重要的子类RuntimeException(运行时异常)

  • ArrayIndexOutOfBoundsException(数组下标越界)

  • NullPointerException(空指针异常)

  • ArithmeticException(算术异常)

  • MissingResourceException(丢失资源)

  • ClassNotFoundException(找不到类)等异常,这些异常是不检查异常,程序中可以选择捕获处理,也可以不处理。

Error和Exception的区别: Error通常是灾难性的致命的错误,是程序无法控制和处理的,当出现这些异常时,Java虚拟机(JVM)一般会选择终止线程;Exception通常情况下是可以被程序处理的,并且在程序中应该尽可能的去处理这些异常。

抛出&捕获异常

异常处理的五个关键字:try、catch、finally、throw、throws

【try...catch...finally】

import java.util.Scanner;

public class Test {
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        int a = input.nextInt();
        int b = input.nextInt();
        try {
            calculate(a, b);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            input.close();
        }
    }
    public static void calculate(int num1, int num2) {
        System.out.println(num1/num2);
    }
}
执行结果:
8
0
java.lang.ArithmeticException: / by zero
	at com.exception.demo01.Test.calculate(Test.java:19)
	at com.exception.demo01.Test.main(Test.java:11)
  • try块中放可能发生异常的代码。
    如果执行完try且不发生异常,则接着去执行finally块和finally后面的代码(如果有的话)。
    如果执行完try发生异常,则尝试去匹配catch块

  • 每一个catch块用于捕获并处理一个特定的异常,或者这异常类型的子类。
    catch后面的括号定义了异常类型和异常参数。如果异常与之匹配且是最先匹配到的,则虚拟机 将使用这个catch块来处理异常。
    如果当前try块中发生的异常在后续的所有catch中都没捕获到,则先去执行finally

  • finally块通常是可选的。 无论异常是否发生,异常是否匹配被处理,finally都会执行。
    finally主要做一些清理工作,如流的关闭,数据库连接的关闭等。

一个try至少要有一个catch块,否则, 至少要有1个finally块。但是finally不是用来处理异常的,finally不会捕获异常

【try...catch...catch...finally】catch可以存在多个,但捕获的类型大小要从小到大

import java.util.Scanner;

public class Test {
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        int a = input.nextInt();
        int b = input.nextInt();
        try {
            calculate(a, b);
        } catch (ArithmeticException e) {
            System.out.println("除数不能为0");
        } catch (Exception f) {
            System.out.println("Exception");
        } catch (Throwable g) {
            System.out.println("Throwable");
            // 类型:ArithmeticException<Exception<Throwable
        }finally {
            input.close();
        }
    }
    public static void calculate(int num1, int num2) {
        System.out.println(num1/num2);
    }
}

throw关键字

可以通过throw语句手动显式的抛出一个异常。throw语句的后面必须是一个异常对象。

throw 语句必须写在方法中,执行throw 语句的地方就是一个异常抛出点

public class Test02 {
    public static void main(String[] args) {
        new Test02().test(5, 0);
    }
    public void test(int a, int b) {
        if (b == 0){
            throw new ArithmeticException();// 主动抛出异常,一般在方法中使用
        }
    }
}
执行结果:
Exception in thread "main" java.lang.ArithmeticException
	at com.exception.demo01.Test02.test(Test02.java:9)
	at com.exception.demo01.Test02.main(Test02.java:5)

throws关键字

throws关键字主要用于方法声明上,指的是当方法之中出现异常后交由被调用处处理

调用了throws声明的方法之后,那么不管操作是否出现异常,都必须使用try...catch语句进行异常处理。 主方法使用throws后,那么这个异常就将交给JVM进行处理,而后结束程序调用。

public class Test03 {
    public static void main(String[] args) {
        try {
            new Test03().division(3, 0);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    public void division(int a, int b) throws Exception {
        System.out.println(a / b);
    }
}
执行结果:
java.lang.ArithmeticException: / by zero
	at com.exception.demo01.Test03.division(Test03.java:13)
	at com.exception.demo01.Test03.main(Test03.java:7)

throw 和 throws的区别:

  • throw: 指的是在方法之中人为抛出一个异常类对象,这个对象可以是自己实例化,或者是已经存在的。
  • throws: 指的是在方法的声明上使用,表示此方法在调用时必须处理异常(使用try...catch语句)。

自定义异常

自定义异常类,则继承Exception类即可

自定义异常类步骤

  1. 创建一个类继承异常父类Exception
  2. 在具体的实现方法首部抛出异常类(自己创建的那个类),throws的运用
  3. 在具体的实现方法的内部抛出异常信息,throw的运用

自定义的异常应该总是包含如下的任意构造函数

  • 一个无参构造函数
  • 一个带有String参数的构造函数,并传递给父类的构造函数。
  • 一个带有String参数和Throwable参数,并都传递给父类构造函数
  • 一个带有Throwable 参数的构造函数,并传递给父类的构造函数
    public MyException() {
        super();
    }

    public MyException(String message) {
        super(message);
    }

    public MyException(String message, Throwable cause) {
        super(message, cause);
    }

    public MyException(Throwable cause) {
        super(cause);
    }

【演示】

public class MyException extends Exception{
    private String name;
    public MyException(String a) {
        this.name = a;
    }
    /*
    toString:异常打印信息
     */
    @Override
    public String toString() {
        return "MyException{" + "name='" + name + '\'' + '}';
    }
}
public class Test {
     public void test(String a) throws MyException {
        if (a == "我不是张三"){
            throw new MyException(a);// 抛出
        }
    }
    public static void main(String[] args) {
        try {
            new Test().test("我不是张三");
        } catch (MyException e) {
            System.out.println(e);
        }
    }
}
执行结果:
MyException{name='我不是张三'}

测试类中异常方法的第二种写法

public class Test02 {
    public void test(String a) {
        if (a == "我不是张三"){
            try {
                throw new MyException(a);
            } catch (MyException e) {
                System.out.println(e);
            }
        }
    }
    public static void main(String[] args) {
        new Test02().test("我不是张三");
    }
}

总结

实际应用中的经验总结

  • 处理运行时异常时,采用逻辑去合理规避同时辅助try-catch处理
  • 在多重catch块后面,可以加一个catch (Exception)来处理可能会被遗漏的异常对于不确定的代码,也可以加上 try-catch,处理潜在的异常
  • 尽量去处理异常,切忌只是简单地调用printStackTrace()去打印输出具体如何处理异常,要根据不同的业务需求和异常类型去决定
  • 尽量添加finally语句块去释放占用的资源
posted @ 2021-01-28 17:22  火车上的老头  阅读(87)  评论(0)    收藏  举报