您的位置 首页 java

Java异常机制的这些知识点你还记得多少?异常机制解析

Java异常机制的这些知识点你还记得多少?异常机制解析

前言

大家好我是 GoKu 时隔多日我们又见面了,上次有粉丝在评论区提到过希望 GoKu 整理一下异常处理机制,于是GoKu抽出时间又一次地回顾了Java异常处理机制相关地内容,通过总结的方式跟大家一起分享学习。

相信很多家人对异常处理并不陌生,且在写代码的时候也常常会碰到过异常,通常情况下我们都需要对发生的异常进行处理,否则JVM会终止程序继续运行,下面我们来一起看看程序中常见的一些异常,下面表格列举了几个常见异常:

Java异常机制的这些知识点你还记得多少?异常机制解析

异常名称异常作用 NullPointException 空指针异常,通常情况下当通过对象调用属性或者方法时如果使用的对象引用为空时会抛出该异常,空指针也是最常遇到的异常之一。 ArrayIndexOutBoundsException 数组指针越界异常,是指当调用数组获取数组元素时,获取的角标位置超出数组长度范围时会抛出异常,程序无法处理该操作,例如:数组的角标范围是[0~10],此时我们想取数组11位置的数时,程序无法处理则抛出异常。 ArithmeticException 算数异常,该异常往往发生在一些运算条件异常的情况,我们最常见的就是除法中的除数为零的情况,例如:10/0,此时除数为零,系统无法处理该运算抛出异常 ClassCastException 类型转换异常,通常发生在,程序中尝试将不同类型进行转换,则系统会抛出该异常 ClassNotFoundException 类找不到异常,程序加载不到某个类时,会抛出该异常

Java异常机制的这些知识点你还记得多少?异常机制解析

Java异常 有很多,这里就不逐一列举了,我们在开发时往往不会刻意地去将所有的异常死记硬背,都是在开发中遇到多了就记下来了,可能刚开始接触编程的家人们在遇到异常的时候总是很头疼,看到密密麻麻一大串红色的堆栈信息,难免会让人心生畏惧感,是的 GoKu 刚开始接触编程的时候也是这样,但是随着开发时长的积累,你会逐渐地觉得,这对你来说并不是什么难事,好了我们不扯那些没用的,接下来 GoKu 跟大家一起来总结一下什么是异常, Java 中的异常体系是怎么样的,异常的捕获,面对异常我们该如何分析堆栈信息以及我们异常中需要注意的点是什么。

第一节:什么是异常?

异常是什么?简单来说就是程序出现了非正常的情况,所谓非正常即异常,用一句话来总结就是:程序发生了不可预期的问题,导致程序无法正常执行,这就是异常。

Java 中,异常机制就是帮助我们在程序发生异常时对异常进行处理的机制,通过异常机制的使用,我们可对代码中可能会发生异常的代码进行修饰、抛出、捕获等进行处理,使得程序在发生异常时能够得到有效处理,提升代码的健壮性。

异常处理机制能够很好的告诉我们发生了什么异常,抛出异常的位置在哪里,以及造成该异常的原因,这使得我们能够很好的通过异常信息定位我们代码发生问题。

内容1:异常的两大类

异常Exception: 指的是程序因某种原因发生的异常,通常情况下开发者可通过异常处理格式进行处理异常。

错误Error:指的是错误,往往发生于 JVM ,错误往往是较为严重的问题,程序本身无法处理,例如内存使用超出了系统分配大大小,此时虚拟机将抛出 OOM(OutOfMemoryError)

内容2:异常继承体系

从上图我们可以看出异常的整体继承体系,异常的顶层父类是 Throwable 类,其中系统定义的异常 Exception 及错误 Error 是为 java 定义的两大异常类型,具体的上边介绍了,我们可以通过直接或间接继承 Error 或者 Exception 来自定义我们的异常。

内容3:异常分类

检查型异常:

检查型异常指的是在编译时期会被编译器检查到的异常,如果开发者不进行处理是不能通过编译的,只有开发者通过 try-catch 语句或者 throws 抛出异常处理过后,方可编译通过。

我们开发中常见的检查型异常有 I/O 读写的时候使用流时,编译器往往会提示开发者,我们传入的文件路径可能找不到,此时会要求我们处理异常。

处理方式一般根据情况进行选择是否处理,如果选择处理则使用 try-catch 语句块进行处理,否则需要使用 throws 关键字进行抛出给调用者处理,如果最终没有人处理,则会抛给虚拟机处理,虚拟机无法处理则会终止程序,所以正常情况下我们会这么处理程序。

运行时异常:

运行时异常指的就是在运行时发生的异常,通常情况下,编译器无法检测到这种异常,,运行时异常为 RuntimeException 异常及继承自它的子类异常,我们最常见的 NullPointException 就是一个运行时异常。由于运行时异常不会被编译器发现,所以导致我们程序在用户的机器上运行时因为用户操作不当导致程序崩溃,这种情况下我们很难发现,所以往往需要我们对代码逻辑要有可预见性,并对代码进行相应的处理才行。

运行时异常发生的位置未知,所以我们写代码需要很仔细,下面我们通过一个小案例来看下代码中存在的隐患。

     //示例代码,定义一个字符串比较的方法 
    public static boolean equals(String str1, String str2) {
        return str1.equals(str2);
    }  

示例代码很简单,定义一个字符串比较的方法,传入两个字符串进行比较,并返回比较结果,这里我们能够发现一些问题,当我们传入的 str1 null 时,此时我们的程序将发生异常,但这段代码编译器是检测不到的。当传入 null 时,程序将抛出 NullPointException

     public static void main(String[] args) {
        boolean isEquals = equals(null, "ss");//故意传错测试代码
        System.out.println(isEquals);
    }

​
    public static boolean equals(String str1, String str2) {
        return str1.equals(str2);
    }  
 执行结果:
Exception in thread "main" java.lang.NullPointerException
    at com.demo.java.MainDemo.equals(MainDemo.java:23)
    at com.demo.java.MainDemo.main(MainDemo.java:17)  

很显然悲剧发生了,系统抛终止了我们的程序,并且会给出我们相关的堆栈信息,使得像这种类型的问题在我们的代码中随处可见,并且有时候是我们不易发现的,所以我们往往需要对代码进行特殊处理,诸如排空之类的操作。

错误:

错误一般是 Error 或者其子类,错误往往都是比较严重的,程序无法进行处理,和运行时异常一样,错误是不会被编译器检查到的,例如我们常见的 OOM 上边也提到过,当程序发生 Error 时,程序本身是无法进行修复的,往往会终止程序运行。

第二节:异常关键字、代码块的介绍与使用

try

try Java 中的表现形式是以代码块的形式出现,主要作用是抛出该作用域内代码发生的异常,换句话说就是,被 try 代码块包裹的代码如果发生异常则会被 try 监听到并抛出相应的异常。

通过 try 捕获了异常(这里说的是异常非 error )程序不会因此而终止,可以通过交给 catch 语句进行处理继续执行。

catch

catch 也是以代码块的形式存在, catch 的作用是用于捕获 try 抛出的异常,此时 catch 代码块会传入相应的异常对象,开发者可以根据异常对象中的信息,在 catch 中对异常进行处理或打印。

finally

finally 最终地, finally 也是以代码块的形式存在,它是一定会被执行的代码块,通常作用是在我们try中使用一些资源完成后,释放这些资源,如( I/O读写、数据库连接、网络连接等等 )。

throw

throw 是抛的意思,是 Java 定义的一个关键字,主要用于抛出异常如:

 throw new RuntimeException("run time exception");  

throws

throws 是抛出,也是 Java 定义的一个关键字,主要用于跟在方法定义尾部,用于在方法上声明可能会抛出异常。

 public static void main(String[] args)throws RuntimeException{}  

接下来介绍一下基本的异常关键字及代码块的使用:

try-catch

     //示例代码
    public static void main(String[] args) {
        int dividend = 10;
        int divisor = 0;
        try {
            int result = dividend / divisor;
            System.out.println("结果:" + result);
        } catch (ArithmeticException e) {
            System.out.println("除数不能为0");
        }
    }  
 运行结果:
除数不能为0  

上边的代码很简单,try包括的语句中出现了算数运算可能出现异常的代码,假设分母为0则计算机无法计算结果,程序异常则会直接抛出异常,接着执行catch中的语句打印 “ 除数不能为0 ”,根据打印结果我们发现了,当代码发生异常时后边的逻辑不会执行,程序将会终止与发生异常的地方,所以控制台并未打印

“结果:” + result 的值。

try-catch-finally

     //示例代码
    public static void main(String[] args) {
        int dividend = 10;
        //除数为0
        int divisor = 0;
        try {
            int result = dividend / divisor;
            System.out.println("结果:" + result);
        } catch (ArithmeticException e) {
            System.out.println("除数不能为0");
        } finally {
            System.out.println("程序执行结束");
        }
    }  
 运行结果:
除数不能为0
程序执行结束  

我们再来看下不发生异常的情况下执行结果是怎样的,我们将divisor的值修改为1。

     //示例代码
    public static void main(String[] args) {
        int dividend = 10;
        int divisor = 1;
        try {
            int result = dividend / divisor;
            System.out.println("结果:" + result);
        } catch (ArithmeticException e) {
            System.out.println("除数不能为0");
        } finally {
            System.out.println("程序执行结束");
        }
    }  
 运行结果:
结果:10
程序执行结束  

我们看到当程序未发生异常时代码不会执行catch语句块中的内容,然后不论是否发生异常,最终都会执行finally代码块中的内容。

第三节:异常信息方法介绍

我们先来制造一个异常来看看他的信息方法都打印了什么?

 try {
    throw new NullPointerException("test Exception");
} catch (Exception e) {
    System.out.println("getMessage: " + e.getMessage());
    System.out.println("getLocalizedMessage: " + e.getLocalizedMessage());
    System.out.println("toString: " + e.toString());
    System.out.println("printStackTrace: ");
    e.printStackTrace();
}  
 运行结果:
getMessage: test Exception 
getLocalizedMessage: test Exception
toString: java.lang.NullPointerException: test Exception
printStackTrace: 
java.lang.NullPointerException: test Exception
    at com.demo.java.MainDemo.main(MainDemo.java:18)  

我们从上边的日志可以看到

getMessage 打印的是异常的详细信息,其实就是异常发生的原因是什么,当我们调用NullPointException的构造函数进行初始化时传入了一个字符串,我们来简单跟下源码:

其实 NullPointException 的构造调用的是父类的构造方法,我们跟到最顶层父类发现其实这个s赋值给了 Throwable 中的 detailMessage 我们来看下代码。

getMessage 方法返回的就是这个 detailMessage ,我们从上面的日志可以发现其实 getMessage getLocalizedMessage 这两个方法打印的东西是一样的,其实 getLocalizedMessage 方法中调用的 getMessage 方法。

上边我们还通过方法 toString 打印了信息,接下来我们看下toString打印了啥:

 toString: java.lang.NullPointerException: test Exception  

其实就是打印了异常类名+异常的详细信息。

那么接下来我们看下最常用的打印异常的方式就是调用异常的printStackTrace()方法,其实这个方法在真正的release环境并不推荐使用,为什么呢?我们来看下这个方法。

 public void printStackTrace() {
    printStackTrace(System.err);
}  

这里调用了内部重载的printStackTrace方法并传入了一个System.err,这个System.err其实是一个PrintStream,是一个输出流,然后我们继续走进printStackTrace方法里看看。

 public final static PrintStream err = null;  

我们看到这个方法是线程安全的,这里家里一个同步代码快,给其中的代码上锁了,走到这里就很清晰了,之所以说在release不推荐使用是因为,这里对性能来说是有一定影响的,尤其是做Android开发的家人都知道的,这里不过多赘述。

所以大家在使用异常对象打印堆栈的时候一定要注意这个点。

try-catch-finally中return顺序

最后我们来说一个异常中的经典问题就是,try-catch-finally语句块中的return语句,也是面试中经常被问到的问题,接下来我们来看看两段代码。

以上两张图,图一的结果是3,图二的结果也是3,为什么都是3呢?不是说return后边的语句不会被执行了吗?是的但是这里其实这里跟finally语句本身的作用有关,首先我们知道执行完try-catch之后一定会执行finally,这就意味着,当try或者catch中return之后,表示这try或者catch代码块执行完了,此时return会在栈内存中分配一个返回值的空间,此时还需要走finally语句,假设finally中有return就会去替换先前栈内存中的那个空间的内容,这个时候return的其实就是finally中返回的值了。

总的概括就是假设finally中有返回值,则finally的return会覆盖try-catch中的return值,最终返回的是finally中的return。

假设finally中没有return则catch覆盖try的return值。

好了一切归于平静,以上就是GoKu个人对异常这块的总结了,可能有不对或者遗漏的,欢迎各位家人们踊跃指出,GoKu一定虚心学习受教改进。

文章来源:智云一二三科技

文章标题:Java异常机制的这些知识点你还记得多少?异常机制解析

文章地址:https://www.zhihuclub.com/188779.shtml

关于作者: 智云科技

热门文章

网站地图