【异常处理】

Java语言中,将程序执行中发生的不正常情况称为“异常”(开发过程中的语法错误和逻辑错误不是异常)

分类

执行过程中所发生的异常事件可分为两大类:

1)Error(错误):Java虚拟机无法解决的严重问题。如:JVM系统内部错误耗尽等严重情况。比如: StackOverflowError[栈溢出]和O0M(out ofmemory),,Error是严重错误,程序会崩溃

2)Exception: 其它因编程错误或偶然的外在因素导致的一般性问题可以使用针对性的代码进行处理。例如空指针访问,试图读取不存在的文件网络连接中断等等,

Exception 分为两大类: 运行时异常[程序运行时]和编译时异常[程序编译时]。

异常处理的方式

try-catch-finally

程序员在代码中捕获发生的异常,自行处理

try {
       //有可能存在异常的代码
    } catch (Exception e) {
        //捕获到异常
    	//1.当异常发生时
    	//2.系统将异常封装成Exception 对象e,传递给catch
    	//3.得到异常对象后,程序员,自己处理
    	//4.注意,如果没有发生异常catch代码块不执行
    }finally{
    	//不管try代码块是否有异常,始终执行finally
    	//通常用来关闭之前使用的资源
    	//没有finally也行
	}
细节

1)如果异常发生了,则异常发生后面的代码不会执行,直接进入到catch块.

public class TryCatchDetail {
    public static void main(String[] args) {

        try {
            String str = "汉堡包";
            int a = Integer.parseInt(str);
            System.out.println("数字:" +a);
        } catch (NumberFormatException e) {
            System.out.println("异常信息=" + e.getMessage());;
        }
        //继续执行
        System.out.println("ing...");

    }
}

2)如果异常没有发生,则顺序执行try的代码块,不会进入到catch.

3)如果希望不管是否发生异常,都执行某段代码(比如关闭连接,释放资源等)

则使用如下代码 - finally {}

4)可以有多个catch语句,捕获不同的异常(进行不同的业务处理),要求父类异
常在后,子类异常在前,比如(Exception在后,NullPointerException在前),如果发生异常,只会匹配个catch

public class TryCatchDetail02 {
    public static void main(String[] args) {

        //1.如果try代码块有可能有多个异常
        //2.可以使用多个catch分别捕获不同的异常,相应处理
        //3.题求子类异常写在前面,父类异常写在后面(即Exception异常因为包含所有异常,所以不能写在最前面)
        try {
            Person person = new Person();
//            person = null;
            System.out.println(person.getName());//NullPointerException
            int n1 = 10;
            int n2 = 0;
            int res = n1/n2;//ArithmeticException
        }catch (NullPointerException e){
            System.out.println("空指针异常=" + e.getMessage());
        }catch (ArithmeticException e){
            System.out.println("算术异常=" + e.getMessage());
        }
        catch (Exception e) {//捕获所有类型的异常
            System.out.println("异常信息=" + e.getMessage());
        }finally {
        }
    }
}
class Person{
    private String name = "jk";

    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}

5)可以进行try-finally配合使用,这种用法相当于没有捕获异常,因此程序会
直接崩掉。应用场景就是执行一段代码,不管是否发生异常,都必须执行某个业务逻辑

public class TryCatchDetail03 {
    public static void main(String[] args) {
        try{
            int n1 = 10;
            int n2 = 0;
            System.out.println(n1 / n2);
        }finally {
            System.out.println("执行了finally.. ");
        }
        System.out.println("程序继续执行..");

    }
}
t-c-f练习

题目:如果用户输入的不是一个整数,就提示他反复输入,直到输入一个整数为止

public class TryCatchExercise04 {
    public static void main(String[] args) {
        //如果用户输入的不是一个整数,就提示他反复输入,直到输入一个整数为止
        //思路:
        //1、要有一个scanner接收输入
        //2、使用无限循环接受输入
        //3、将输入的值转换为int
        //4、如果在转换时捕获到异常,说明输入的不是一个可以转为int的内容
        //5、如果不爆出异常,则break结束循环
        Scanner scanner = new Scanner(System.in);
        int num = 0;
        String inputStr = "";
        while (true){
            System.out.println("请输入一个整数:");
            inputStr = scanner.next();
            try {
                num = Integer.parseInt(inputStr);//此处有可能抛出异常
                break;
            } catch (NumberFormatException e) {
                System.out.println("输入的不是整数");
            }
        }
        System.out.println("输入的整数为:" + num);
    }
}

throws

将发生的异常抛出,交给调用者(方法)来处理,最顶级的处理者就是JVN

image-20221213211820203

怎么理解?
其实上图以及解释得很清楚了,即当前方法(假设是f2)出现异常,那么f2有两种处理方式,如果你在f2中写了try-catch-finally(后续即为t-c-f),那么就按照你catch中的规定处理,如果你没写,那就throws到调用f2的上一级f1(没写t-c-f就会默认throws),f1处理流程同理,直到将异常给到JVM(或者你在某个部分将其处理完毕)

到JVM后,它只会输出异常,然后中断程序

如果你也在使用Python那么应该会对这个过程有感受

很多时候报错是可以一直往后推,进而找到最开始出错的地方的

基本介绍

1)如果一个方法(中的语句执行时)可能生成某种异常,但是并不能确定如何处理这种异常,则此方法应显示地声明抛出异常,表明该方法将不对这些异常进行处理,而由该方法的调用者负责处理。

public class Throws01 {
    public static void main(String[] args) {

    }
    public void f1() throws FileNotFoundException {
        //创建一个文件流对象
        //这里存在一个FileNotFoundException异常
        //使用throws去处理,让调用f1方法的调用者(一般是另外一个方法)来处理
        FileInputStream fils = new FileInputStream("d://aa.jpg");
    }
}

2)在方法声明中用throws语句可以声明抛出异常的列表,throws后面的异常类型可以是方法中产生的异常类型,也可以是它的父类。

public class Throws01 {
    public static void main(String[] args) {

    }
    public void f1() throws FileNotFoundException, NullPointerException, ArithmeticException
{//或者你嫌麻烦可以写Exception
        //创建一个文件流对象
        //这里存在一个FileNotFoundException异常
        //使用throws去处理,让调用f1方法的调用者(一般是另外一个方法)来处理
        //throws后面可以跟一个列表
        FileInputStream fils = new FileInputStream("d://aa.jpg");
    }
}
细节

1)对于编译异常,程序中必须处理,比如try-catch或者throws

2)对于运行时异常,程序中如果没有处理,默认就是throws的方式处理

3)子类重写父类的方法时,对抛出异常的规定:子类重写的方法,所抛出的异常类型要么和父类抛出的异常一致,要么为父类抛出的异常的类型的子类型

class Father { lI父类
public void method() throws RuntimeException {
	}
class Son extends Father {//子类
//3.子类重写父类的方法时,对抛出异常的规定:子类重写的方法,
//  所抛出的异常类型要么和父类抛出的异常一致,要么为父类抛出的异常类型的子类型
@Override
public void method() throws NullPointerException {
	}
}
//NullPointerException是RuntimeException的子类

4)在throws过程中,如果有方法 try-catch,就相当于处理异常,就可以不必throws

public class ThrowsDetail {
    public static void main(String[] args) {
        f2();
    }

    public static void f2() /*throws ArithmeticException*/ {
        //1.对于编译异常,程序中必须处理,比如 try-catch 或者 throws
        //2.对于运行时异常,程序中如果没有处理,默认就是throws的方式处理
        int n1 = 10;
        int n2 = 0;
        double res = n1 / n2;
    }

    public static void f1() throws FileNotFoundException {
        //这里调用f3()会报错
        //1. 因为f3() 方法抛出的是一个编译异常
        //2. 即这时,就要f1() 必须处理这个编译异常
        //3. 在f1() 中,要么 try-catch-finally ,或者继续throws 这个编译异常
        f3(); // 抛出异常
    }
    public static void f3() throws FileNotFoundException {
        FileInputStream fis = new FileInputStream("d://aa.txt");
    }

    public static void f4() {
        //1. 在f4()中调用方法f5() 是OK
        //2. 原因是f5() 抛出的是运行异常
        //3. 而java中,并不要求程序员显示处理,因为有默认处理机制
        f5();
    }
    public static void f5() throws ArithmeticException {
    }
}

自定义异常

当程序中出现了某些“错误”,但该错误信息并没有在Throwable子类中描述处理,这个时候可以自己设计昇常类,用于描述该错误信息。

自定义异常的步骤

1)定义类:自定义异常类名(程序员自己写),需要继承Exception或RuntimeException

2)如果继承Exception,属于编译异常

3)如果继承RuntimeException,属于运行异常(一般来说,继承RuntimeException)

实例

public class CustomException {
    public static void main(String[] args) /*throws AgeException*/{
        int age = 80;
        if(!(age>=18 && age<=120)){
            //注意这里是throw,而不是throws
            throw new AgeException("年龄需要在18~120之间");
        }
        System.out.println("done");
    }
}
//自定义异常【其实就是继承了系统异常的一个特殊的类】
//1.一般情况下,我们自定义异常会继承RuntimeException
//2.即把自定义异常做成运行时异常,好处是,我们可以使用默认的处理机制,比较方便
class AgeException extends RuntimeException{
    public AgeException(String message) {//构造器接受一个输入
        super(message);
    }
}

throw和throws的区别

作用 位置 后面跟的对象
throw 手动生成异常对象,关键字 方法体中 异常对象
throws 异常处理的一种方式 方法声明处 异常类型

异常练习

a)编写应用程序HomeWork01.java,接收命令行的两个参数(整数),计算两数相除【注意是接收参数,不是接收输入】
b)计算两个数相除,要求使用方法cal(int n1, int n2)
c)对数据格式不正确、缺少命令行参数、除0进行异常处理
public class HomeWork01 {
    public static void main(String[] args) {
        try {
            //因为cal需要两个运行参数,所以先验证有没有给到2个参数
            if(args.length ! = 2){
                throw new ArrayIndexOutOfBoundsException("参数个数需为两个");//如果在后面没有catch,就直接JVM处理了
            }
            //把拿到的参数转为整数
            int n1 = Integer.parseInt(args[0]);
            int n2 = Integer.parseInt(args[1]);

            double res = cal(n1,n2);//可能报ArithmeticException
            System.out.println("res = " + res);
        }catch (ArrayIndexOutOfBoundsException e) {
            System.out.println(e.getMessage());;
        }catch (NumberFormatException e){
            System.out.println("参数需要是整数");
        }catch (ArithmeticException e){
            System.out.println("分母不能为零");
        }
    }

    public static double cal(int n1, int n2){
        return n1 / n2;
    }
}