异常处理的学习原理初学解读(1万字吐血推荐,还包含少许面试题的奥)

系列链接:
异常处理的学习的目的到底是什么?
异常处理的学习原理解读


我们直接进入主题,看看都有哪些常见的处理异常的格式。

try+catch处理流程

1、 一旦产生异常,则系统会自动产生一个异常类的实例化对象。
2、 那么,此时如果异常发生在try语句,则会自动找到匹配的catch语句执行,如果没有在try语句中,则会将异常抛出。抛给调用这个方法的人,抛给调用产生异常方法的代码位置
3、 所有的catch根据方法的参数匹配异常类的实例化对象,如果匹配成功,则表示由此catch进行处理。

/**
 * 处理多异常的格式 2   了解
 */
public class Demo3 {
    public static void main(String[] args){

        heiheihei();
        System.out.println("程序执行完毕 , 正常结束");
    }

    private static void heiheihei() {
        try {
            Scanner input = new Scanner(System.in);
            System.out.println("请输入一个数字");
            int x = input.nextInt();
            System.out.println("请再输入一个数字");
            int y = input.nextInt();
            System.out.println(x / y);
            System.out.println("处理完毕");
        }catch(InputMismatchException|ArithmeticException e){
            System.out.println("输入有误");
        }
    }
}

那大家可以看到,我其实在上面的注释中写的是了解,没错,其实单一的try-catch是比较少见的,所以在这里只是为了向大家引进这个方法的概念。看看就可以。
那我们再来看看这个Demo3的运行结果是什么样的。在这里插入图片描述
同样的5/0,现在我们通过try-catch将原本爆红的错误变成了打印的一句话。好我先不进行分析,让我们看看其他的格式的效果后,我再一起进行比较。

多重try-catch处理过程

我们光听名字应该就知道这是一个什么格式了吧,直接上代码。

/**
 * 处理多异常的格式 1 熟悉
 */
public class Demo2 {
    public static void main(String[] args){
        heiheihei();
        System.out.println("程序执行完毕 , 正常结束");
    }

    private static void heiheihei() {
        try {
            Scanner input = new Scanner(System.in);
            System.out.println("请输入一个数字");
            int x = input.nextInt();
            System.out.println("请再输入一个数字");
            int y = input.nextInt();
            System.out.println(x / y);
            System.out.println("处理完毕");
        }catch(InputMismatchException e){
            System.out.println("必须输入数字啊, 帅哥");
        }catch(ArithmeticException e){
            System.out.println("除数不能为0啊 , 帅哥");
        }
    }
}

那因为我们将原本在一个catch方法中的两个异常给分成了,各一个异常,现在我们可以对其进行更细化的描述。
在这里插入图片描述在这里插入图片描述
当条件不一样的时候,打印的结果也会不同。
那其实我们可以感受到,这样打印一次结束一次会很麻烦,那我们的单层其实也可以做到这样的效果,接下来就再多展示一个案例让大家更能容易的理解。

public class Test1 {
    public static void main(String[] args) {
        int num = menu();
        System.out.println("你输入的是:"+num);
    }

    public static int menu() {
        System.out.println("请根据提示,选择功能序号:");
        System.out.println("1. 增加XX");
        System.out.println("2. 删除XX");
        System.out.println("3. 修改XX");
        System.out.println("0. 退出");

        Scanner input = new Scanner(System.in);
        int num = -1;//不合理的值
        try {
            num = input.nextInt();
            if (num<0 || num>3){
                //程序有问题,输入有误
                System.out.println("数字序号必须是: 0/1/2/3");
                return menu();
            }
            return num;
        }catch (InputMismatchException e) {
            //System.out.println("哈啊哈哈哈啊哈哈哈哈哈");
            //补救
            System.out.println("必须输入数字哦");
            return menu();
        }
    }
}

运行截图:
在这里插入图片描述
哎,这就很舒服。

好,我们接下来看最要掌握的处理异常的格式是什么样的。

try-catch-finally处理异常

如果要想对异常进行处理,则应该采用标准的处理格式,处理格式语法如下:

		try{//try中出现异常
			// 有可能发生异常的代码段
		}catch(异常类型1 对象名1){//catch中做补救操作
			// 异常的处理操作
		}catch(异常类型2 对象名2){
			// 异常的处理操作
		} ...
		finally{
			// 异常的统一出口
		}

案例:

/**
 * 处理多异常的格式 3    常用
 */
public class Demo4 {
    public static void main(String[] args){
        heiheihei();
        System.out.println("程序执行完毕 , 正常结束");
    }

    private static void heiheihei() {
        try {
            Scanner input = new Scanner(System.in);
            System.out.println("请输入一个数字");
            int x = input.nextInt();
            System.out.println("请再输入一个数字");
            int y = input.nextInt();
            System.out.println(x / y);
            System.out.println("处理完毕");
        }catch(Exception e){//直接catch所有异常,扩大异常的形态范围,增加效率
            System.out.println("输入有误");
        }finally {
            //必然执行的异常统一处理出口
            //无论是否发生异常, finally必然执行.
            System.out.println("213123");
        }
    }
}

我们这里其实细心地同学们有注意到,我在catch块的异常是一个Exception异常,它其实是我们的上一节说过的,是我们所有异常的父类,也就是说,你只要把异常写成Exception,所有异常的抛出都不会报错,但是不建议大家去使用,哪怕当我们在以后的工作中会为了节省自己的脑力,为了省事,全都直接抛出给Exception,但是现阶段,我们是初学者,还是多进行针对性的练习,多了解一些异常更好。
好,继续进行,我们看一下运行结果。
在这里插入图片描述
这里也就是finally块的意义所在,我们不管进行正确与否也就是是否发生异常, finally必然执行。

定义与用法

那我们在看过这三种的处理异常格式,想来对他们都有了一定的看法与了解,那我们再回过头来去说他们的定义域用法,来帮助大家进行更进一步的加深。

try/catch/finally 语句用于处理代码中可能出现的错误信息。
错误可能是语法错误,通常是程序员造成的编码错误或错别字。也 可能是拼写错误或语言中缺少的功能(可能由于浏览器差异)基本不会发生。
在这里插入图片描述各自的含义作用:
try语句允许我们定义在执行时进行错误测试的代码块。
catch语句允许我们定义当 try 代码块发生错误时,所执行的代码块。(这里可以不输出,只抛出异常)
finally 语句在 try 和 catch 之后无论有无异常都会执行。(建议都写上,因为全面而且也是后手)
注意: catch 和 finally 语句都是可选的,但你在使用 try 语句时必须至少使用一个。
提示: 当错误发生时, 我们的程序会停止执行,并生成一个错误信息。使用 throw 语句 来创建自定义消息(抛出异常)。如果你将 throwtrycatch一起使用,就可以控制程序输出的错误信息。(throw之后会讲,那我们用throw的作用就是将异常抛出)。

引用数据类型

接下来,我介绍一下我们的两种数据类型在异常处理中的处理过程。

/**
 * Demo6引用数据类型、
 * 找同文件夹下引用数据类型。png
 */
public class Demo6 {
    public static void main(String[] args) {
        Person p = heiheihei();
        System.out.println(p.age);
    }
    public  static  Person heiheihei(){
        Person p = new Person();
        //程序执行过程,我们准备好了return的这个数据p,但还没有return
        //他是怎么准备的呢?把这个数据p(里面包含年龄18的Person)准备好了,
        // 复制了一份,复制的是这个p(Person的引用地址)
        //然后在他即将返回的时候,准备好返回值还没有返回的时候finally块执行了
        try{
            p.age = 19;
            return p;
        }catch (Exception e){
            return null;
        }finally {//finally在try块赋值19后return返回的之前执行了,
            // 所以age被改为29了,最后返回打印的p.age为29
            p.age = 29;
        }

    }
    static  class Person{
        int age;
    }
}

运行截图如下:
在这里插入图片描述
为什么是29?它怎么会执行了finally中的代码呢,他不应该是调用了heiheihei方法就执行try块,将19赋值给age,返回给p对象直接就返回到main方法打印出来吗?
很明显,我们的结果告诉我们不是这样的,那到底是什么样的,为什么这样进行呢?

注意我的注释,我直接把他拿出来让大家更好的观看。
这是重点,是我们异常处理的原理。
程序执行过程,我们准备好了return的这个数据p,但还没有return
他是怎么准备的呢?把这个数据p(里面包含年龄18的Person)准备好了,
复制了一份,复制的是这个p(Person的引用地址)
然后在他即将返回的时候,准备好返回值还没有返回的时候finally块执行了

为了更好的理解,我画了一张图。
请添加图片描述这也是为什么它会执行finally块代码的原因。

基本数据类型

那在看过我们的引用数据类型后来对比一下基本数据类型。

/**
 * 引用数据类型与非引用数据类型之间的差异
 * Demo7基本(非)引用数据类型
 * 找同文件夹下基本引用数据类型。png
 */
public class Demo7 {
    public static void main(String[] args) {
        int a = haha();
        System.out.println(a);
    }
    public static int haha(){
        int a = 10;
        try{
            return a;
        //在他准备return这个a的时候,这个a已经不是上面的这个a了
        //而是新备份的a,他准备好了返回值,是10,要返回了
        //准备执行finally,这个时候finally再改a,和准备好的这个数据没有关系
        }catch(Exception e){
        }finally {
            a = 20;
        }
        return 0;//程序出口
    }
    static class Person{
        int age;
    }
}

运行截图如下:
在这里插入图片描述
哎,这次为什么又是10,没有执行finally中的代码呢?
注意我的注释,我直接把他拿出来让大家更好的观看。
这是重点,是我们异常处理的原理。
在他准备return这个a的时候,这个a已经不是上面的这个a了
而是新备份的a,他准备好了返回值,是10,要返回了
准备执行finally,这个时候finally再改a,和准备好的这个数据没有关系。

我还画了一张图便于大家的理解。

请添加图片描述结合前面的注释,进行两者之间的比较,我们可以了解他们的原理了。

中断异常

那有时候我们不想将异常往下进行了,我们就想着在中间出现异常的时候,把他打印出来,告诉我一下,这里出现异常了,我表示明白,但我就是不处理。当出现这种想法时,我们应该怎么做呢?
这里就要涉及一个方法,exit方法,作用和名字一样,就是出口,结束。
那我们来看一下它的案例:


/**
 * 该方法的原型是:System.exit(int status).
 *
 *   取值及作用
 *   status由使用者自取,一般可取0,1或者其他数;
 * 	 当其为System.exit(0)时,正常退出当前程序,关闭虚拟机(JVM);
 *   当其为System.exit(1),或者其他值的时候,则非正常退出程序,关闭虚拟机。
 *
 *   用法
 *   java中的main方法是静态的,这点和c++不同;
 *   其关键字void表示的是没有返回值,不会为操作系统返回退出代码。
 *   如果main方法正常退出,则退出代码为“0”,
 *   如果希望在终止时返回其他代码,就需要调用System.exit方法。
 * 
 *   status无论为何值都会退出程序,关闭虚拟机,
 *   只不过为“0”时,退出正常,为其他值的时候,退出异常,
 *   可以用在catch块中,将System.exit(1)放进去,用来表示非正常退出
 */
public class Demo8 {
    public static void main(String[] args) {
        haha();
    }
    public static void haha(){
        try{
            int a = 10;
            int b = 0;
            System.out.println(a/b);
        }catch(Exception e){
            //退出JVM
            System.out.println("出现了异常");
             System.exit(0);//0表示正常退出,其他的表示不正常
         
        }finally {
            System.out.println("锄禾日当午,汗滴禾下土");
        }
    }
}

运行截图如下:
在这里插入图片描述

exit的原理

它是直接在catch块打印出异常时,exit退出了。就连我们之前说的一定会执行的final语句也没有在执行。那这是为什么?

我们将注释拿出来。一步一步的说。

该方法的原型是:System.exit(int status).
在这里插入图片描述上面的那两行我就不再翻译了,大家自行了解,大致就是存在一个security管理器就要抛出SecurityException,不能正常抛出(英语水平有限,手动狗头)。那我们就不深入了,我们看下面的。
在这里插入图片描述
这里的Runtime注释的意思就是其他任何无法实例化这个类。
那我们的API其实也有讲到这一点
在这里插入图片描述我们的getRuntime返回的currentRuntime,说的也很清楚,获得当前运行时。
在这里插入图片描述
用我的话,它是由final关键字修饰的一个方法,那其实最后的输出结果就是,当前执行位置,之后如果存在exit()方法则直接退出程序。

exit的取值及作用

那我们继续说exit的取值。

我们解释一下exit(0)的正常退出。
正常退出,是指如果当前程序还有在执行的任务,则等待所有任务执行完成以后再退出;
非正常退出,只要时间到了,立刻停止程序运行,不管是否还有任务在执行。这个效果就类似于linux 里面的 kill -9 和 kill -15

用法

java中的main方法是静态的,这点和c++不同;其关键字void表示的是没有返回值,不会为操作系统返回退出代码。 如果main方法正常退出,则退出代码为“0”, 如果希望在终止时返回其他代码,就需要调用System.exit方法。status无论为何值都会退出程序,关闭虚拟机, 只不过为“0”时,退出正常,为其他值的时候,退出异常,可以用在catch块中,将System.exit(1)放进去,用来表示非正常退出。

正常和非正常的用法,为了举例而举例!: 比如在一个try-catch[-finally]语句体中,如果程序是按照预期的执行,到最后如果需要停止程序。那么就可以使用System.exit(0);表示正常退出! 而System.exit(1);一般都是放在catch语句中,当程序发生了错误,如果需要停止程序,那么就可以使用Systeml.exit(1),这时status=非0且整数 则表示这个程序非正常退出!

这里我总结一下当我们面试时关于处理异常的面试题:

少许面试题

  1. Java中的异常类型有哪些?
    分为受检查异常(非运行时异常)与非受检异常(运行时异常)
  2. finally在某某某种情况下是否执行?
    1、必然执行
    如果面试官说的这种情况是程序被关闭了,程序结束了,软件在内存里没了,电脑停电了…finally才不会执行否则finally必然执行
    2、return的时机
    try-catch-finally 中,如果 catch 中 return 了,finally 还会执行吗?
    答:finally中的代码会执行
    详解:
    执行流程:
    1. 先计算返回值, 并将返回值存储起来, 等待返回
    2. 执行finally代码块
    3. 将之前存储的返回值, 返回出去;
    需注意:
    1. 返回值是在finally运算之前就确定了,并且缓存了,不管finally对该值做任何的改变,返回的值都不会改变
    2. finally代码中不建议包含return,因为程序会在上述的流程中提前退出,也就是说返回的值不是try或catch中的值
    3. 如果在try或catch中停止了JVM,则finally不会执行.例如停电- -, 或通过如下代码退出JVM:System.exit(0);

throws关键字

在程序中异常的基本处理已经掌握了,但是随异常一起的还有一个称为throws关键字,此关键字主要在方法的声明上使用,表示方法中不处理异常,而交给调用处处理。
格式:
返回值 方法名称()throws Exception{
}

public class Demo9 {
    public static void main(String[] args) {
        try {
            shutdown("");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 异常是否抛出去, 应该站在哪个角度思考?
     *
     *  如果是因为传参导致异常 , 应该通过throws将异常抛出去.
     *
     * @param text
     * @throws IOException : 因为传递的指令不对, 会导致此问题发生
     */
    public static void shutdown(String text) throws IOException {
        Runtime.getRuntime().exec(text);
    }
    /**
     * 此方法用于求两个参数的和
     *  会将两个参数 转换为数字 求和
     * @param s1  字符串参数1
     * @param s2  字符串参数2
     */
    public static void sum(String s1,String s2){
        int sum = Integer.parseInt(s1)+Integer.parseInt(s2);
        System.out.println("和是:"+sum);
    }
}

那我们的运行截图:
在这里插入图片描述
那直接就是报出异常,但是我们可以其实不知道我们那个Empty command到底代表着什么,那这个其实我们是可以自定义的。

自定义异常

在 Java 中你可以自定义异常。编写自己的异常类时需要记住下面的几点。

  • 所有异常都必须是 Throwable 的子类。
  • 如果希望写一个检查性异常类,则需要继承 Exception 类。
  • 如果你想写一个运行时异常类,那么需要继承 RuntimeException 类
    首先我们定义一个Person类:
public class Person {
    private String name;
    private int age;

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        if(age<0 || age>180){
            RuntimeException e = new RuntimeException("年龄不合理");
            throw e;//告诉出错
        }else{
            this.age = age;
        }

    }
}

这里其实涉及到了我们的throw关键字。
throw关键字
throw关键字表示在程序中人为的抛出一个异常,因为从异常处理机制来看,所有的异常一旦产生之后,实际上抛出
的就是一个异常类的实例化对象,那么此对象也可以由throw直接抛出。
代码: throw new Exception(“抛着玩的。”) ;
那我们就直接在实例中使用了。
然后自定义我们的异常

//AgeRuntimeException
public class AgeRuntimeException extends RuntimeException{
//这是一个检查性的异常,我们等会通过控制台输出去看
    public AgeRuntimeException(String message) {
        super(message);
    }
}

那我们主要关注年龄这一块,我们去设置一个异常的抛出,throw关键字告诉出错。
之后我们就进行测试,
测试类:

public class Demo10 {
    public static void main(String[] args) {
        Person p = new Person();
        p.setAge(-1);//-1是不正常的
    }
}

运行截图如下:
在这里插入图片描述哎,这就很舒服了,我们自定义异常,然后将我们的异常原因给打印了出来。一目了然,清清楚楚,当然,我们在往后的代码编写中很少使用这个自定义异常,因为java的异常都说的很明白了,我们只需要记住主要的一些异常就好了。那我们再主要说一下java内置异常类,这个建议大家记住。

异常类有两个主要的子类:IOException 类和 RuntimeException 类
在这里插入图片描述在 Java 内置类中(接下来会说明),有大部分常用检查性和非检查性异常。

Java 内置异常类

Java 语言定义了一些异常类在 java.lang 标准包中。

标准运行时异常类的子类是最常见的异常类。由于 java.lang 包是默认加载到所有的 Java 程序的,所以大部分从运行时异常类继承而来的异常都可以直接使用。
Java 根据各个类库也定义了一些其他的异常,下面的表中列出了 Java 的非检查性异常

异常描述
ArithmeticException当出现异常的运算条件时,抛出此异常。例如,一个整数"除以零"时,抛出此类的一个实例。
ArrayIndexOutOfBoundsException用非法索引访问数组时抛出的异常。如果索引为负或大于等于数组大小,则该索引为非法索引。
ArrayStoreException试图将错误类型的对象存储到一个对象数组时抛出的异常。
ClassCastException当试图将对象强制转换为不是实例的子类时,抛出该异常。
IllegalArgumentException抛出的异常表明向方法传递了一个不合法或不正确的参数。
IllegalMonitorStateException抛出的异常表明某一线程已经试图等待对象的监视器,或者试图通知其他正在等待对象的监视器而本身没有指定监视器的线程。
IllegalStateException在非法或不适当的时间调用方法时产生的信号。换句话说,即 Java 环境或 Java 应用程序没有处于请求操作所要求的适当状态下。
IllegalThreadStateException线程没有处于请求操作所要求的适当状态时抛出的异常。
IndexOutOfBoundsException指示某排序索引(例如对数组、字符串或向量的排序)超出范围时抛出。
NegativeArraySizeException如果应用程序试图创建大小为负的数组,则抛出该异常。
NullPointerException当应用程序试图在需要对象的地方使用 null 时,抛出该异常(经常出现)
NumberFormatException当应用程序试图将字符串转换成一种数值类型,但该字符串不能转换为适当格式时,抛出该异常。(格式转换也很常见)
SecurityException由安全管理器抛出的异常,指示存在安全侵犯。
StringIndexOutOfBoundsException此异常由 String 方法抛出,指示索引或者为负,或者超出字符串的大小。
UnsupportedOperationException当不支持请求的操作时,抛出该异常。

下面的表中列出了 Java 定义在 java.lang 包中的检查性异常类

异常描述
ClassNotFoundException应用程序试图加载类时,找不到相应的类,抛出该异常(常见)
CloneNotSupportedException当调用 Object 类中的 clone 方法克隆对象,但该对象的类无法实现 Cloneable 接口时,抛出该异常。
IllegalAccessException拒绝访问一个类的时候,抛出该异常。
InstantiationException当试图使用 Class 类中的 newInstance 方法创建一个类的实例,而指定的类对象因为是一个接口或是一个抽象类而无法实例化时,抛出该异常。
InterruptedException一个线程被另一个线程中断,抛出该异常。
NoSuchFieldException请求的变量不存在
NoSuchMethodException请求的方法不存在

那到这里我们的异常处理的有关内容就基本讲述完毕。

若本篇内容对您有所帮助,请三连点赞,关注,收藏支持下。

创作不易,白嫖很爽,但是求各位手下留情,留个言再拿走好不好。同志们的支持和认可,就是我创作的最大动力。

少年,又是你 | 文

如果本篇博客有任何错误或者疏漏,请批评斧正,感激不尽 !
如果想要API帮助学习的,API的链接:最新版API JDK11版本中文释义

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

少年,又是你

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值