一文搞懂Java异常是个什么东东?

2022年01月14日 阅读数:3
这篇文章主要向大家介绍一文搞懂Java异常是个什么东东?,主要内容包括基础应用、实用技巧、原理机制等方面,希望对大家有所帮助。

1、Java异常简介 

你们对trycatch可能并不陌生,也都使用的很是熟练了。java

当程序运行过程当中发生错误时,就会抛出异常,抛出异常总比终止程序来的好的多。jvm

也能够在已知某个错误要发生时,进行trycatch操做,异常时进行某些特有操做。函数

一、Exception和Error

Exception和Error都继承于Throwable 类,在 Java 中只有 Throwable 类型的实例才能够被抛出或捕获,它是异常处理机制的基本组成类型。性能

Exception是可预料的异常状况,能够获取到这种异常,并对其进行业务外的处理。优化

Error是不可预料的异常,error发生后,会直接致使JVM不可处理。spa

Exception分为检查性异常、非检查性异常。线程

检查性异常,必须在编写代码时,使用try catch捕获(好比:IOException异常)。指针

非检查性异常,编译器不会发现这个地方是否会产生一次,好比空指针异常,这种异常是在代码编写或者使用过程当中经过规范能够避免发生的。好比sts的findbugs功能就能够检测到代码的空指针异常。日志

二、NoClassDefFoundError 和 ClassNotFoundException 有什么区别?

NoClassDefFoundError是JVM运行时经过classpath加载类时,找不到对应的类而抛出的错误。code

ClassNotFoundException:若是在编译过程当中可能出现此异常,在编译过程当中必须将其抛出。

NoClassDefFoundError的发生场景:

  1. 类依赖的class或jar不存在
  2. 类文件存在,可是在不一样的域中,简而言之,就是找不到

ClassNotFoundException的发生场景:

  1. 调用class的forName方法时,找不到指定的类
  2. ClassLoader中的findSystemClass() 方法时,找不到指定的类
public static void main(String[] args) {
    try {
        Class.forName("test");
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    }
}

2、trycatch语法

一、try语句

try语句用大括号包含一段代码,该段代码可能会抛出一个或多个例外。

二、catch语句

catch语句的参数相似于方法的声明,包括一个例外类型和一个例外对象。例外类型必须为Throwable类的子类,它指明了catch语句所处理的例外类型,例外对象则由运行时系统在try所指定的代码块中生成并被捕获,大括号中包含对象的处理,其中能够调用对象的方法。

catch语句能够有多个,分别处理不一样类的例外。Java运行时系统从上到下分别对每一个catch语句处理的例外类型进行检测,直到找到类型相匹配的catch语句为止。这里,类型匹配指catch所处理的例外类型与生成的例外对象的类型彻底一致或者是它的父类,所以,catch语句的排列顺序应该是从特殊到通常。

三、finally语句

无论try中是否会抛出例外,finally语句中的代码都会执行,finally 语句块的最重要的做用应该是释放申请的资源。

四、throws语句

throws老是出如今函数头后,用来标明该方法可能抛出的异常。

五、throw语句

与throws殊途同归,只是位置不一样,throw放在catch模块中,程序会在throw执行后当即终止,throw后的代码不执行了,finally除外。

六、抛出异常

public void test() throws Exception{
    throw new Exception();
};

七、捕获异常

try{
    //代码区
}catch(Exception e){
    log.error("error: {}", e);
}finally{
    //最后必须执行的部分
}

3、trycatch的执行顺序

  1. 从try中第一行代码开始执行,执行到出现异常的代码,JVM会建立一个异常对象。
  2. 判断catch是否能捕获到jvm建立的异常对象,① 若是捕获到就跳到catch代码块中,不会结束程序,继续从catch中的代码逻辑;② 若是捕获不到,直接打印异常信息并结束程序。
  3. 若是try中没有异常,则执行完try中代码,跳过catch,进入finally代码块。

4、异常处理原则

  1. 方法内若是抛出须要检测的异常,那么方法上必需要声明,不然必须在方法内用try-catch捕捉,不然编译失败。
  2. 若是调用了声明异常的函数,要么try-catch要么throws,不然编译失败。
  3. 何时catch,何时throws?功能内容能够解决,用catch,解决不了,用throws告诉调用者,有调用者解决。
  4. 若是一个功能抛出了多个异常,那么调用时必须有对应多个catch进行针对性的处理。

5、工做过程当中总结的使用try的注意事项

  1. 尽可能不要直接捕获Exception,而是应该先捕获特定异常,最后捕获Exception异常。由于写代码也是一门学问,要让读代码的人能够从代码中获取更多的信息。
  2. try中不该放过多的代码,只放必须放的就能够了,放太多的话若是发生异常,异常后面的代码将不会被执行。
  3. catch能够有多个,而捕获的异常,应按优先级捕获,先捕获最低级别的异常,以此类推。
  4. finally在trycatch语法中是选用的,没必要须有,但若是有,不论是否发生异常,finally中的代码都会最后执行,finally中常见的操做是对流进行关闭,或者删除某些操做完的文件等。
  5. 当你对一段代码进行trycatch时,catch中必定要作处理,通常会给定返回值,boolean类型、整形通常根据实际状况返回-一、String或者集合类型通常返回null,而后再调用处进行返回值判断,坚定避免catch中不作任何处理,亦或返回了,但没接收。
  6. 当有业务须要时,能够用trycatch进行逻辑判断,报错时是一种逻辑的判断,有的时候很方便,也很好用。

6、禁用e.printStackTrace() 

禁止在代码中出现e.printStackTrace() 。

e.printStackTrace() 只会将错误信息打印在控制台(通常用log输出日志替代),产生的错误字符串会到字符串内存空间,此内存空间一旦被占满,就没空间进行其它操做了,大量其它的线程就会停止,等待内存空间的释放,相互等待,等内存,锁住了,整个应用就挂掉了。

7、trycatch内代码越少越好吗?

民间有这样一个说法,trycatch的范围越小越好,这个谁都知道,为何呢?由于效率低。为何效率低呢?不知道了。

trycatch与没有trycatch的代码,区别在于,前者阻止了Java的重排序,trycatch里面的代码不会被编译器优化重排。重排序是编译器在不改变单线程程序语义的前提下,能够从新安排语句的执行顺序。

简而言之,try会阻止Java对代码进行重排序,也就是放弃了Java对代码的优化。效率确定比重排序低,因此说,try中的代码越少越好。

固然,若是你不能很好的把握哪里会爆异常,哪里不会,在保证代码正确运行的前提下,再去尝试性的缩小trycatch的范围吧。

而代码的重排序最关键的地方就是即时编译器,下面来介绍一下即时编译器,也是本篇的重点。

8、即时编译器

即时编译器JIT是一个把Java字节码转换成能够直接发送给处理器的执行令的程序,用于提升运行时Java应用程序的性能。

在运行时,JVM装载类文件,肯定每一个单独的字节码的含义,并执行相应的计算,解释期间额外使用处理器和内存就意味着Java应用程序的执行速度要比本机应用程序慢,这是确定的,由于代码还须要编译嘛。

JIT编译器经过在运行时将字节码编译为本机代码以帮助提升Java程序的性能。

在编译方法时,JVM直接调用编译好的本地代码,而不须要再去编译了。理论上,若是编译不须要占用处理器时间和内存,那么编译每一个方法均可能使Java程序的执行速度接近于本机应用程序的速度。

实际上,第一次调用方法时不会对方法进行编译。对于每一个方法,JVM都会保留一个调用计数,每次调动方法时该计数都将递增,JVM对方法进行解释,直至其调用计数超过JIT编译阈值。所以,JVM启动后将当即编译经常使用方法,而在较长时间以后再编译不经常使用方法,或者直接不编译不经常使用方法。JIT编译阈值帮助JVM快速启动而且还能提升性能,谨慎选择阈值,在启动和长期运行之间实现最佳平衡。

在编译方法后,调用计数将重置为0,而且对该方法的后续调用将继续使其计数递增。在达到阈值时,JIT编译器将执行第二次编译,与前一次的编译相比,其优化选择更多,此过程循环往复,直至达到最大优化级别。Java程序中被调用次数最多的方法,代码的优化处理作的是最好的,这也是提倡提取共通方法的缘由。

简而言之,就是执行越频繁的代码,Java对其的优化越好,执行速度越快。

9、trycatch小demo

一、代码实例

public class ExceptionTest {
    private boolean test01() {
        boolean ret = true;
        try {
            ret = test02();
        } catch (Exception e) {
            System.out.println("test01 error:" + e.getMessage());
            ret = false;
        } finally {
            System.out.println("test01,finally, return -> " + ret);
            return ret;
        }
    }
 
    private boolean test02() {
        boolean ret = true;
        try {
            //问题1:test03发生异常了,虽然trycatch了,但调用的地方没有接收
            test03();
            System.out.println("由于test03报错,我不该该执行。");
            return ret;
        } catch (Exception e) {
            System.out.println("test02 error:"+e.getMessage());
            ret = false;
            throw e;
        } finally {
            System.out.println("test02 finally, return -> " + ret);
            return ret;
        }
    }
 
    private boolean test03() throws Exception {
        boolean ret = true;
        try {
            System.out.println("我是CSDN哪吒");
            System.out.println("即将发生异常");
            int a = 1/0;
            System.out.println("发生异常后,还有走我吗?");
            return true;
        } catch (Exception e) {
            System.out.println("test03 error:"+e.getMessage());
            ret = false;
            throw e;
        } finally {
            System.out.println("test03 finally, return -> " + ret);
            return ret;
        }
    }
 
    public static void main(String[] args) {
        ExceptionTest main = new ExceptionTest();
        try {
            main.test01();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

二、控制台输出

三、思考一个问题

当须要在循环中使用trycatch的时候,try放for外面好,仍是里面好呢?

当二者没有发生异常时,二者的效率实际上是同样的。

10、常见异常

一、Exception常见的子类

二、RuntimeException常见的子类

三、Error类的常见子类