京东-优惠雷达
新人页面
精选商品
首月0月租体验,领12个月京东PLUS
自营热卖

Java秋招,稳了

明月照影归 24天前   阅读数 37 0

💂 个人主页: Java程序鱼
🤟 整个Java 体系的面试题我都会分享,大家可以持续关注
💬 如果文章对你有帮助、欢迎关注、点赞、收藏(一键三连)和订阅专栏哦
💅 有任何问题欢迎私信,看到会及时回复!

序号 内容 链接地址
1 Java基础知识面试题 https://blog.csdn.net/qq_35620342/article/details/119636436
2 Java集合容器面试题 待分享
3 Java并发编程面试题 待分享
4 Java异常面试题 https://blog.csdn.net/qq_35620342/article/details/119977051
5 JVM面试题 待分享
6 Java Web面试题 https://blog.csdn.net/qq_35620342/article/details/119642114
7 Spring面试题 https://blog.csdn.net/qq_35620342/article/details/119956512
8 Spring MVC面试题 https://blog.csdn.net/qq_35620342/article/details/119965560
9 Spring Boot面试题 待分享
10 MyBatis面试题 https://blog.csdn.net/qq_35620342/article/details/119956541
11 Spring Cloud面试题 待分享
12 Redis面试题 https://blog.csdn.net/qq_35620342/article/details/119575020
13 MySQL数据库面试题 https://blog.csdn.net/qq_35620342/article/details/119930887
14 RabbitMQ面试题 待分享
15 Dubbo面试题 待分享
16 Linux面试题 待分享
17 Tomcat面试题 待分享
18 ZooKeeper面试题 待分享
19 Netty面试题 待分享
20 数据结构与算法面试题 待分享


1.Java异常架构

在这里插入图片描述

2.Throwable

  • Throwable 类是 Java 语言中所有错误或异常的超类。
  • 只有当对象是此类(或其子类之一)的实例时,才能通过 Java 虚拟机或者 Java throw 语句抛出。类似地,只有此类或其子类之一才可以是 catch 子句中的参数类型。
  • Throwable 包含了其线程创建时线程执行堆栈的快照。它还包含了给出有关错误更多信息的消息字符串。
  • 最后,它还可以包含 cause(原因):另一个导致此 throwable 抛出的 throwable。此 cause 设施在 1.4 版本中首次出现。它也称为异常链 设施,因为 cause 自身也会有 cause,依此类推,就形成了异常链,每个异常都是由另一个异常引起的。

3.Error(错误)

  • Error 是 Throwable 的子类,用于指示合理的应用程序不应该试图捕获的严重问题。
  • 大多数这样的错误都是异常条件。虽然 ThreadDeath 错误是一个“正规”的条件,但它也是 Error 的子类,因为大多数应用程序都不应该试图捕获它。
  • 在执行该方法期间,无需在其 throws 子句中声明可能抛出但是未能捕获的 Error 的任何子类,因为这些错误可能是再也不会发生的异常条件。
  • Java 程序通常不捕获错误。错误一般发生在严重故障时,它们在Java程序处理的范畴之外。

4.Exception(异常)

Exception 异常主要分为两类

  • 一类是 IOException(I/O 输入输出异常),其中 IOException 及其子类异常又被称作「受查异常」
  • 另一类是 RuntimeException(运行时异常),RuntimeException 被称作「非受查异常」。
    受查异常就是指,编译器在编译期间要求必须得到处理的那些异常,你必须在编译期处理了。

5.常见的非检查性异常

编译器要求必须处理的异常。正确的程序在运行过程中,经常容易出现的、符合预期的异常情况。一旦发生此类异常,就必须采用某种方式进行处理。除 RuntimeException 及其子类外,其他的 Exception 异常都属于受检异常。编译器会检查此类异常,也就是说当编译器检查到应用中的某处可能会此类异常时,将会提示你处理本异常——要么使用try-catch捕获,要么使用方法签名中用 throws 关键字抛出,否则编译不通过。
在这里插入图片描述

6.常见的检查性异常

编译器不会进行检查并且不要求必须处理的异常,也就说当程序中出现此类异常时,即使我们没有try-catch捕获它,也没有使用throws抛出该异常,编译也会正常通过。该类异常包括运行时异常(RuntimeException极其子类)和错误(Error)。
在这里插入图片描述

7.Java异常关键字

(1)try…catch关键字

  • 使用 try 和 catch 关键字可以捕获异常。
  • try/catch 代码块放在异常可能发生的地方。

try/catch代码块中的代码称为保护代码,使用 try/catch 的语法如下:

try {
     
   // 程序代码
} catch(ExceptionName e1) {
     
   //Catch 块
}
  • Catch 语句包含要捕获异常类型的声明。当保护代码块中发生一个异常时,try 后面的 catch 块就会被检查。如果发生的异常包含在 catch 块中,异常会被传递到该 catch 块,这和传递一个参数到方法是一样。
  • 一个 try 代码块后面跟随多个 catch 代码块的情况就叫多重捕获。
  • 多重捕获块的语法如下所示:
try{
     
   // 程序代码
}catch(异常类型1 异常的变量名1){
     
  // 程序代码
}catch(异常类型2 异常的变量名2){
     
  // 程序代码
}catch(异常类型2 异常的变量名2){
     
  // 程序代码
}

(2) throws/throw 关键字

  • 如果一个方法没有捕获一个检查性异常,那么该方法必须使用 throws 关键字来声明。throws 关键字放在方法签名的尾部。也可以使用 throw 关键字抛出一个异常,无论它是新实例化的还是刚捕获到的。
  • 下面方法的声明抛出一个 RemoteException 异常:
public class className {
     
  public void deposit(double amount) throws RemoteException {
     
    // Method implementation
    throw new RemoteException();
  }
  //Remainder of class definition
}

一个方法可以声明抛出多个异常,多个异常之间用逗号隔开。

(3)finally关键字

  • finally 关键字用来创建在 try 代码块后面执行的代码块。
  • 无论是否发生异常,finally 代码块中的代码总会被执行。在 finally 代码块中,可以运行清理类型等收尾善后性质的语句。
  • finally 代码块出现在 catch 代码块最后,语法如下:
try{
     
  // 程序代码
}catch(异常类型1 异常的变量名1){
     
  // 程序代码
}catch(异常类型2 异常的变量名2){
     
  // 程序代码
}finally{
     
  // 程序代码
}

8.声明异常

通常,应该捕获那些知道如何处理的异常,将不知道如何处理的异常继续传递下去。传递异常可以在方法签名处使用 throws 关键字声明可能会抛出的异常。

注意

  • 非检查异常(Error、RuntimeException 或它们的子类)不可使用 throws 关键字来声明要抛出的异常。
  • 一个方法出现编译时异常,就需要 try-catch/ throws 处理,否则会导致编译错误。

9.抛出异常

如果你觉得解决不了某些异常问题,且不需要调用者处理,那么你可以抛出异常。

throw关键字作用是在方法内部抛出一个Throwable类型的异常。任何Java代码都可以通过throw语句抛出异常。

10.捕获异常

程序通常在运行之前不报错,但是运行后可能会出现某些未知的错误,但是还不想直接抛出到上一级,那么就需要通过try…catch…的形式进行异常捕获,之后根据不同的异常情况来进行相应的处理。

11.常见异常处理方式

(1)直接抛出异常

通常,应该捕获那些知道如何处理的异常,将不知道如何处理的异常继续传递下去。传递异常可以在方法签名处使用 throws 关键字声明可能会抛出的异常。

private static void readFile(String filePath) throws IOException {
     
    File file = new File(filePath);
    String result;
    BufferedReader reader = new BufferedReader(new FileReader(file));
    while((result = reader.readLine())!=null) {
     
        System.out.println(result);
    }
    reader.close();
}

(2)封装异常再抛出

有时我们会从 catch 中抛出一个异常,目的是为了改变异常的类型。多用于在多系统集成时,当某个子系统故障,异常类型可能有多种,可以用统一的异常类型向外暴露,不需暴露太多内部异常细节。

private static void readFile(String filePath) throws MyException {
         
    try {
     
        
    } catch (IOException e) {
     
        MyException ex = new MyException("read file failed.");
        ex.initCause(e);
        throw ex;
    }
}

12.捕获异常

在一个 try-catch 语句块中可以捕获多个异常类型,并对不同类型的异常做出不同的处理

private static void readFile(String filePath) {
     
    try {
     
        
    } catch (FileNotFoundException e) {
     
        
    } catch (IOException e){
     
        
    }
}

同一个 catch 也可以捕获多种类型异常,用 | 隔开

private static void readFile(String filePath) {
     
    try {
     
        
    } catch (FileNotFoundException | UnknownHostException e) {
     
        
    } catch (IOException e){
     
        
    }
}

13.自定义异常

习惯上,定义一个异常类应包含两个构造函数,一个无参构造函数和一个带有详细描述信息的构造函数(Throwable 的 toString 方法会打印这些详细信息,调试时很有用)

public class MyException extends Exception {
     
    public MyException(){
      }
    public MyException(String msg){
     
        super(msg);
    }
    
}

14.try-catch-finally

当方法中发生异常,异常处之后的代码不会再执行,如果之前获取了一些本地资源需要释放,则需要在方法正常结束时和 catch 语句中都调用释放本地资源的代码,显得代码比较繁琐,finally 语句可以解决这个问题。

private static void readFile(String filePath) throws MyException {
     
    File file = new File(filePath);
    String result;
    BufferedReader reader = null;
    try {
     
        reader = new BufferedReader(new FileReader(file));
        while((result = reader.readLine())!=null) {
     
            System.out.println(result);
        }
    } catch (IOException e) {
     
        System.out.println("readFile method catch block.");
        MyException ex = new MyException("read file failed.");
        ex.initCause(e);
        throw ex;
    } finally {
     
        System.out.println("readFile method finally block.");
        if (null != reader) {
     
            try {
     
                reader.close();
            } catch (IOException e) {
     
                e.printStackTrace();
            }
        }
    }
}

调用该方法时,读取文件时若发生异常,代码会进入 catch 代码块,之后进入 finally 代码块;若读取文件时未发生异常,则会跳过 catch 代码块直接进入 finally 代码块。所以无论代码中是否发生异常,fianlly 中的代码都会执行。

若 catch 代码块中包含 return 语句,finally 中的代码还会执行吗?将以上代码中的 catch 子句修改如下:

catch (IOException e) {
     
    System.out.println("readFile method catch block.");
    return;
}

调用 readFile 方法,观察当 catch 子句中调用 return 语句时,finally 子句是否执行

readFile method catch block.
readFile method finally block.

可见,即使 catch 中包含了 return 语句,finally 子句依然会执行。若 finally 中也包含 return 语句,finally 中的 return 会覆盖前面的 return.

15.try-with-resource

上面例子中,finally 中的 close 方法也可能抛出 IOException, 从而覆盖了原始异常。JAVA 7 提供了更优雅的方式来实现资源的自动释放,自动释放的资源需要是实现了 AutoCloseable 接口的类。

private  static void tryWithResourceTest(){
     
    try (Scanner scanner = new Scanner(new FileInputStream("c:/abc"),"UTF-8")){
     
        
    } catch (IOException e){
     
        
    }
}

try 代码块退出时,会自动调用 scanner.close 方法,和把 scanner.close 方法放在 finally 代码块中不同的是,若 scanner.close 抛出异常,则会被抑制,抛出的仍然为原始异常。被抑制的异常会由 addSusppressed 方法添加到原来的异常,如果想要获取被抑制的异常列表,可以调用 getSuppressed 方法来获取。

16.Error 和 Exception 区别是什么?

Error类和Exception类的父类都是throwable类,他们的区别是:

Error类一般是指与虚拟机相关的问题,如系统崩溃,虚拟机错误,内存空间不足,方法调用栈溢等。对于这类错误的导致的应用程序中断,仅靠程序本身无法恢复和预防,遇到这样的错误,建议让程序终止。

Exception类表示程序可以处理的异常,可以捕获且可能恢复。遇到这类异常,应该尽可能处理异常,使程序恢复运行,而不应该随意终止异常。

Exception类又分为运行时异常(Runtime Exception)和受检查的异常(Checked Exception ),运行时异常;ArithmaticException,IllegalArgumentException,编译能通过,但是一运行就终止了,程序不会处理运行时异常,出现这类异常,程序会终止。而受检查的异常,要么用try…catch捕获,要么用throws字句声明抛出,交给它的父类处理,否则编译不会通过。

17.Java 虚拟机是如何捕获异常的?

在编译生成的字节码中,每个方法都附带一个异常表,异常表中的每一个条目代表一个异常处理器,并且由 form 指针,to 指针,target 指针以及所捕获的异常类型构成。这些指针的值是字节码索引,用来定位字节码。

from 指针和 to 指针标示了该异常所监控的范围:try 代码块所覆盖的范围。
target 指针标示了异常处理器的起始位置:catch 代码块的起始位置。

当程序处罚异常时,Java 虚拟机会从上至下遍历异常表中的所有条目。当触发异常的字节码索引值在某个异常表条目的监控范围内,Java 虚拟机再判断所抛出的异常和该条目想要捕获的异常是否匹配。如果匹配,Java 虚拟机会将控制流转移至该条目 target 指针指向的字节码。

如果遍历完异常表的条目未曾匹配到异常处理器,那么它会弹出当前方法对应的 Java 栈帧,并且在调用者中重复上述操作。

finally 代码块的编译比较复杂。当前版本 Java 编译器的做法:复制 finally 代码块内容,分别放在 try-catch 代码块所有正常执行路径以及异常执行路径的出口中。

在这里插入图片描述
针对异常执行路径,Java 编译器会生成一个(上图变种2)或者多个(上图变种1)异常表条目,监控整个 try-catch 代码块,并且捕获所有种类的异常。这些异常表条目的 target 指针将指向另一份复制的 finally 代码块(上图变种1,变种2 中红色 finally block),并且重新抛出捕获的异常。

问题:如果 catch 代码块捕获了异常,并且触发了另一个异常,那么 finally 捕获并且重抛的异常是 catch 代码块触发的新的异常,原本的异常就被忽略了。这对代码调试来说,就不友好了。

Java 7 中引出了 Supressed 异常来解决上面的问题。这个新特性允许开发人员将一个异常附在另一个异常上,这样抛出的异常就可以附带多个异常的信息。

问答
Q:为什么使用异常捕获的代码比较耗费性能
单从 Java 语法上看不出来,但是从 JVM 实现的细节上来看就明白了。构造异常实例,需要生成该异常的栈轨迹。该操作会逐一访问当前线程的栈帧,记录各种调试信息,包括类名,方法名,触发异常的代码行数等等。

Q:finally 是怎么实现无论异常与否都能执行
编译器在编译代码时会复制 finally 代码块放在 try-catch 代码块所有正常执行路径以及异常执行路径的出口处。

Q:finally 中有 ruturn 语句,catch 中抛出的异常会被忽略,为什么
catch 抛出的异常会被 finally 捕获,执行完 finally 后会重新抛出该异常。由于 finally 中有 return 语句,在重新抛出异常之前,代码就已经返回了。

Q:方法的异常表都包含哪些异常
方法的异常表只声明这段代码会被捕获的异常,而且是非检查异常。如果 catch 中有自定义异常,那么异常表中也会包含自定义异常的条目。

Q:检查异常和非检查异常也就是其他书籍中说的编译期异常和运行时异常?
检查异常也会在运行过程中抛出。但是它会要求编译器检查代码有没有显式地处理该异常。非检查异常包括Error和RuntimeException,这两个则不要求编译器显式处理。

18. throw 和 throws 的区别是什么?

Java 中的异常处理除了包括捕获异常和处理异常之外,还包括声明异常和拋出异常,可以通过 throws 关键字在方法上声明该方法要拋出的异常,或者在方法内部通过 throw 拋出异常对象。

throws 关键字和 throw 关键字在使用上的几点区别如下

  • throw 关键字用在方法内部,只能用于抛出一种异常,用来抛出方法或代码块中的异常,受查异常和非受查异常都可以被抛出。
  • throws 关键字用在方法声明上,可以抛出多个异常,用来标识该方法可能抛出的异常列表。一个方法用 throws 标识了可能抛出的异常列表,调用该方法的方法中必须包含可处理异常的代码,否则也要在方法签名中用 throws 关键字声明相应的异常。

19.final、finally、finalize 有什么区别?

final是一种声明属性,作用与类、方法和变量;final修饰的变量的值不能够再改变,final修饰的方面不能被覆盖,final修饰的类不能被继承。

finally是异常处理语句的一部分,表示总是执行。

finalize是object类的一个子方法,在垃圾回收器执行是调用回收对象的此方法,此方法可以被覆盖提供资源回收时的其他资源回收。如文件关闭等。

20.NoClassDefFoundError 和 ClassNotFoundException 区别?

在写Java程序的时候,当一个类找不到的时候,JVM有时候会抛出 ClassNotFoundException 异常,而有时候又会抛出 NoClassDefFoundError。看两个异常的字面意思,好像都是类找不到,但是JVM为什么要用两个异常去区分类找不到的情况呢?这个两个异常有什么不同的地方呢?

(1)ClassNotFoundException

ClassNotFoundException 是一个运行时异常。从类继承层次上来看,ClassNotFoundException 是从 Exception 继承的,所以 ClassNotFoundException 是一个检查异常。

当应用程序运行的过程中尝试使用类加载器去加载Class文件的时候,如果没有在 classpath 中查找到指定的类,就会抛出 ClassNotFoundException 。一般情况下,当我们使用Class.forName()或ClassLoader.loadClass()或ClassLoader.findSystemClass()在运行时加载类的时候,如果类没有被找到,那么就会导致JVM抛出 ClassNotFoundException。

最简单的,当我们使用JDBC去连接数据库的时候,我们一般会使用Class.forName()的方式去加载JDBC的驱动,如果我们没有将驱动放到应用的 classpath 下,那么会导致运行时找不到类,所以运行Class.forName()会抛出 ClassNotFoundException。

public class MainClass {
     
    public static void main(String[] args) {
     
        try {
     
            Class.forName("oracle.jdbc.driver.OracleDriver");
        } catch (ClassNotFoundException e) {
     
            e.printStackTrace();
        }
    }
}

输出结果:

$ java MainClass
java.lang.ClassNotFoundException: oracle.jdbc.driver.OracleDriver
    at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
    at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:331)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
    at java.lang.Class.forName0(Native Method)
    at java.lang.Class.forName(Class.java:264)
    at MainClass.main(MainClass.java:7)

(2)NoClassDefFoundError

NoClassDefFoundError 异常,看命名后缀是一个 Error 。从类继承层次上看,NoClassDefFoundError 是从 Error 继承的。和 ClassNotFoundException 相比,明显的一个区别是,NoClassDefFoundError 并不需要应用程序去关心捕获的问题。

当JVM在加载一个类的时候,如果这个类在编译时是可用的,但是在运行时找不到这个类的定义的时候,JVM就会抛出一个 NoClassDefFoundError 错误。比如当我们在 new 一个类的实例的时候,如果在运行是类找不到,则会抛出一个 NoClassDefFoundError 的错误。

public class TempClass {
     
}

public class MainClass {
     
    public static void main(String[] args) {
     
        TempClass t = new TempClass();
    }
}

输出结果:

$ java MainClass
Exception in thread "main" java.lang.NoClassDefFoundError: TempClass
    at MainClass.main(MainClass.java:6)
Caused by: java.lang.ClassNotFoundException: TempClass
    at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
    at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:331)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
    ... 1 more

(3)总结

ClassNotFoundException NoClassDefFoundError
从java.lang.Exception继承,是Exception类型 从java.lang.Error继承,是Error类型
当动态加载Class的时候找不到类会抛出ClassNotFoundException异常 当编译成功以后执行过程中Class找不到导致抛出NoClassDefFoundError错误
一般在执行Class.forName()、ClassLoader.loadClass()或ClassLoader.findSystemClass()的时候抛出 由JVM的运行时抛出

21.try-catch-finally 中哪个部分可以省略?

答:catch 可以省略

原因

更为严格的说法其实是:try只适合处理运行时异常,try+catch适合处理运行时异常+普通异常。也就是说,如果你只用try去处理普通异常却不加以catch处理,编译是通不过的,因为编译器硬性规定,普通异常如果选择捕获,则必须用catch显示声明以便进一步处理。而运行时异常在编译时没有如此规定,所以catch可以省略,你加上catch编译器也觉得无可厚非。

理论上,编译器看任何代码都不顺眼,都觉得可能有潜在的问题,所以你即使对所有代码加上try,代码在运行期时也只不过是在正常运行的基础上加一层皮。但是你一旦对一段代码加上try,就等于显示地承诺编译器,对这段代码可能抛出的异常进行捕获而非向上抛出处理。如果是普通异常,编译器要求必须用catch捕获以便进一步处理;如果运行时异常,捕获然后丢弃并且+finally扫尾处理,或者加上catch捕获以便进一步处理。

至于加上finally,则是在不管有没捕获异常,都要进行的“扫尾”处理。

22.try-catch-finally 中,如果 catch 中 return 了,finally 还会执行吗?

答:会执行,在 return 前执行。

注意:在 finally 中改变返回值的做法是不好的,因为如果存在 finally 代码块,try中的 return 语句不会立马返回调用者,而是记录下返回值待 finally 代码块执行完毕之后再向调用者返回其值,然后如果在 finally 中修改了返回值,就会返回修改后的值。显然,在 finally 中返回或者修改返回值会对程序造成很大的困扰,C#中直接用编译错误的方式来阻止程序员干这种龌龊的事情,Java 中也可以通过提升编译器的语法检查级别来产生警告或错误。

代码示例1:

public static int getInt() {
     
    int a = 10;
    try {
     
        System.out.println(a / 0);
        a = 20;
    } catch (ArithmeticException e) {
     
        a = 30;
        return a;
        
    } finally {
     
        a = 40;
    }
	return a;
}

执行结果:30

代码示例2:

public static int getInt() {
     
    int a = 10;
    try {
     
        System.out.println(a / 0);
        a = 20;
    } catch (ArithmeticException e) {
     
        a = 30;
        return a;
    } finally {
     
        a = 40;
        
        return a; 
    }

}

执行结果:40

23.类 ExampleA 继承 Exception,类 ExampleB 继承ExampleA

有如下代码片断:

try {
     
	throw new ExampleB("b")
} catchExampleA e){
     
	System.out.println("ExampleA");
} catchException e){
     
	System.out.println("Exception");
}

请问执行此段代码的输出是什么?

输出:ExampleA。(根据里氏代换原则[能使用父类型的地方一定能使用子类型],抓取 ExampleA 类型异常的 catch 块能够抓住 try 块中抛出的 ExampleB 类型的异常)

面试题 - 说出下面代码的运行结果。

class Annoyance extends Exception {
     
}
class Sneeze extends Annoyance {
     
}
class Human {
     
	public static void main(String[] args)
	throws Exception {
     
		try {
     
			try {
     
				throw new Sneeze();
			} catch ( Annoyance a ) {
     
				System.out.println("Caught Annoyance");
				throw a;
			}
		} catch ( Sneeze s ) {
     
			System.out.println("Caught Sneeze");
			return ;
		} finally {
     
			System.out.println("Hello World!");
		}
	}
}

结果

Caught Annoyance
Caught Sneeze
Hello World!

24.常见的 RuntimeException 有哪些?

NullPointerException,空指针引用异常
ClassCastException,类型强制转换异常。
IllegalArgumentException,传递非法参数异常。
ArithmeticException,算术运算异常
ArrayStoreException,向数组中存放与声明类型不兼容对象异常
IndexOutOfBoundsException,下标越界异常
NegativeArraySizeException,创建一个大小为负数的数组错误异常
NumberFormatException,数字格式异常
SecurityException,安全异常
UnsupportedOperationException,不支持的操作异常

25.Java常见异常有哪些?

ArithmeticExecption
算术异常类

NullPointerException
空指针异常类

ClassCastException
类型强制转换异常

NegativeArrayException
数组负下标异常

ArrayIndexOutOfBoundsException
数组下标越界异常

SecturityException
违背安全原则异常

EOFException
文件已结束异常

FileNotFoundException
文件未找到异常

NumberFormatException
字符串转换为数字异常

SQLException
操作数据库异常

IOException
输入输出异常

NoSuchMethodException
方法未找到异常

java.lang.AbstractMethodError
抽象方法错误。当应用试图调用抽象方法时抛出。

java.lang.AssertionError
断言错。用来指示一个断言失败的情况。

java.lang.ClassCircularityError
类循环依赖错误。在初始化一个类时,若检测到类之间循环依赖则抛出该异常。

java.lang.ClassFormatError
类格式错误。当Java虚拟机试图从一个文件中读取Java类,而检测到该文件的内容不符合类的有效格式时抛出。

java.lang.Error
错误。是所有错误的基类,用于标识严重的程序运行问题。这些问题通常描述一些不应被应用程序捕获的反常情况。

java.lang.ExceptionInInitializerError
初始化程序错误。当执行一个类的静态初始化程序的过程中,发生了异常时抛出。静态初始化程序是指直接包含于类中的static语句段。

java.lang.IllegalAccessError
违法访问错误。当一个应用试图访问、修改某个类的域(Field)或者调用其方法,但是又违反域或方法的可见性声明,则抛出该异常。

java.lang.IncompatibleClassChangeError
不兼容的类变化错误。当正在执行的方法所依赖的类定义发生了不兼容的改变时,抛出该异常。一般在修改了应用中的某些类的声明定义而没有对整个应用重新编译而直接运行的情况下,容易引发该错误。

java.lang.InstantiationError
实例化错误。当一个应用试图通过Java的new操作符构造一个抽象类或者接口时抛出该异常.

java.lang.InternalError
内部错误。用于指示Java虚拟机发生了内部错误。

java.lang.LinkageError
链接错误。该错误及其所有子类指示某个类依赖于另外一些类,在该类编译之后,被依赖的类改变了其类定义而没有重新编译所有的类,进而引发错误的情况。

java.lang.NoClassDefFoundError
未找到类定义错误。当Java虚拟机或者类装载器试图实例化某个类,而找不到该类的定义时抛出该错误。

java.lang.NoSuchFieldError
域不存在错误。当应用试图访问或者修改某类的某个域,而该类的定义中没有该域的定义时抛出该错误。

java.lang.NoSuchMethodError
方法不存在错误。当应用试图调用某类的某个方法,而该类的定义中没有该方法的定义时抛出该错误。

java.lang.OutOfMemoryError
内存不足错误。当可用内存不足以让Java虚拟机分配给一个对象时抛出该错误。

java.lang.StackOverflowError
堆栈溢出错误。当一个应用递归调用的层次太深而导致堆栈溢出时抛出该错误。

java.lang.ThreadDeath
线程结束。当调用Thread类的stop方法时抛出该错误,用于指示线程结束。

java.lang.UnknownError
未知错误。用于指示Java虚拟机发生了未知严重错误的情况。

java.lang.UnsatisfiedLinkError
未满足的链接错误。当Java虚拟机未找到某个类的声明为native方法的本机语言定义时抛出。

java.lang.UnsupportedClassVersionError
不支持的类版本错误。当Java虚拟机试图从读取某个类文件,但是发现该文件的主、次版本号不被当前Java虚拟机支持的时候,抛出该错误。

java.lang.VerifyError
验证错误。当验证器检测到某个类文件中存在内部不兼容或者安全问题时抛出该错误。

java.lang.VirtualMachineError
虚拟机错误。用于指示虚拟机被破坏或者继续执行操作所需的资源不足的情况。

java.lang.ArithmeticException
算术条件异常。譬如:整数除零等。

java.lang.ArrayIndexOutOfBoundsException
数组索引越界异常。当对数组的索引值为负数或大于等于数组大小时抛出。

java.lang.ArrayStoreException
数组存储异常。当向数组中存放非数组声明类型对象时抛出。

java.lang.ClassCastException
类造型异常。假设有类A和B(A不是B的父类或子类),O是A的实例,那么当强制将O构造为类B的实例时抛出该异常。该异常经常被称为强制类型转换异常。

java.lang.ClassNotFoundException
找不到类异常。当应用试图根据字符串形式的类名构造类,而在遍历CLASSPAH之后找不到对应名称的class文件时,抛出该异常。

java.lang.CloneNotSupportedException
不支持克隆异常。当没有实现Cloneable接口或者不支持克隆方法时,调用其clone()方法则抛出该异常。

java.lang.EnumConstantNotPresentException
枚举常量不存在异常。当应用试图通过名称和枚举类型访问一个枚举对象,但该枚举对象并不包含常量时,抛出该异常。

java.lang.Exception
根异常。用以描述应用程序希望捕获的情况。

java.lang.IllegalAccessException
违法的访问异常。当应用试图通过反射方式创建某个类的实例、访问该类属性、调用该类方法,而当时又无法访问类的、属性的、方法的或构造方法的定义时抛出该异常。

java.lang.IllegalMonitorStateException
违法的监控状态异常。当某个线程试图等待一个自己并不拥有的对象(O)的监控器或者通知其他线程等待该对象(O)的监控器时,抛出该异常。

java.lang.IllegalStateException
违法的状态异常。当在Java环境和应用尚未处于某个方法的合法调用状态,而调用了该方法时,抛出该异常。

java.lang.IllegalThreadStateException
违法的线程状态异常。当县城尚未处于某个方法的合法调用状态,而调用了该方法时,抛出异常。

java.lang.IndexOutOfBoundsException
索引越界异常。当访问某个序列的索引值小于0或大于等于序列大小时,抛出该异常。

java.lang.InstantiationException
实例化异常。当试图通过newInstance()方法创建某个类的实例,而该类是一个抽象类或接口时,抛出该异常。

java.lang.InterruptedException
被中止异常。当某个线程处于长时间的等待、休眠或其他暂停状态,而此时其他的线程通过Thread的interrupt方法终止该线程时抛出该异常。

java.lang.NegativeArraySizeException
数组大小为负值异常。当使用负数大小值创建数组时抛出该异常。

java.lang.NoSuchFieldException
属性不存在异常。当访问某个类的不存在的属性时抛出该异常。

java.lang.NoSuchMethodException
方法不存在异常。当访问某个类的不存在的方法时抛出该异常。

java.lang.NullPointerException
空指针异常。当应用试图在要求使用对象的地方使用了null时,抛出该异常。譬如:调用null对象的实例方法、访问null对象的属性、计算null对象的长度、使用throw语句抛出null等等。

java.lang.NumberFormatException
数字格式异常。当试图将一个String转换为指定的数字类型,而该字符串确不满足数字类型要求的格式时,抛出该异常。

java.lang.RuntimeException
运行时异常。是所有Java虚拟机正常操作期间可以被抛出的异常的父类。

java.lang.SecurityException
安全异常。由安全管理器抛出,用于指示违反安全情况的异常。

java.lang.StringIndexOutOfBoundsException
字符串索引越界异常。当使用索引值访问某个字符串中的字符,而该索引值小于0或大于等于序列大小时,抛出该异常。

java.lang.TypeNotPresentException
类型不存在异常。当应用试图以某个类型名称的字符串表达方式访问该类型,但是根据给定的名称又找不到该类型是抛出该异常。该异常与ClassNotFoundException的区别在于该异常是unchecked(不被检查)异常,而ClassNotFoundException是checked(被检查)异常。

java.lang.UnsupportedOperationException
不支持的方法异常。指明请求的方法不被支持情况的异常。

26.在 finally 块中清理资源或者使用 try-with-resource 语句

当使用类似InputStream这种需要使用后关闭的资源时,一个常见的错误就是在try块的最后关闭资源。

public void doNotCloseResourceInTry() {
     
    FileInputStream inputStream = null;
    try {
     
        File file = new File("./tmp.txt");
        inputStream = new FileInputStream(file);
        
        
        inputStream.close();
    } catch (FileNotFoundException e) {
     
        log.error(e);
    } catch (IOException e) {
     
        log.error(e);
    }
}

问题就是,只有没有异常抛出的时候,这段代码才可以正常工作。try 代码块内代码会正常执行,并且资源可以正常关闭。但是,使用 try 代码块是有原因的,一般调用一个或多个可能抛出异常的方法,而且,你自己也可能会抛出一个异常,这意味着代码可能不会执行到 try 代码块的最后部分。结果就是,你并没有关闭资源。

所以,你应该把清理工作的代码放到 finally 里去,或者使用 try-with-resource 特性。

27.使用 finally 代码块

与前面几行 try 代码块不同,finally 代码块总是会被执行。不管 try 代码块成功执行之后还是你在 catch 代码块中处理完异常后都会执行。因此,你可以确保你清理了所有打开的资源。

public void closeResourceInFinally() {
     
    FileInputStream inputStream = null;
    try {
     
        File file = new File("./tmp.txt");
        inputStream = new FileInputStream(file);
        
    } catch (FileNotFoundException e) {
     
        log.error(e);
    } finally {
     
        if (inputStream != null) {
     
            try {
     
                inputStream.close();
            } catch (IOException e) {
     
                log.error(e);
            }
        }
    }
}

28.Java 7 的 try-with-resource 语法

如果你的资源实现了 AutoCloseable 接口,你可以使用这个语法。大多数的 Java 标准资源都继承了这个接口。当你在 try 子句中打开资源,资源会在 try 代码块执行后或异常处理后自动关闭。

public void automaticallyCloseResource() {
     
    File file = new File("./tmp.txt");
    try (FileInputStream inputStream = new FileInputStream(file);) {
     
        
    } catch (FileNotFoundException e) {
     
        log.error(e);
    } catch (IOException e) {
     
        log.error(e);
    }
}

29.优先明确的异常

你抛出的异常越明确越好,永远记住,你的同事或者几个月之后的你,将会调用你的方法并且处理异常。

因此需要保证提供给他们尽可能多的信息。这样你的 API 更容易被理解。你的方法的调用者能够更好的处理异常并且避免额外的检查。

因此,总是尝试寻找最适合你的异常事件的类,例如,抛出一个 NumberFormatException 来替换一个 IllegalArgumentException 。避免抛出一个不明确的异常。

public void doNotDoThis() throws Exception {
     
    ...
}
public void doThis() throws NumberFormatException {
     
    ...
}

30.对异常进行文档说明

当在方法上声明抛出异常时,也需要进行文档说明。目的是为了给调用者提供尽可能多的信息,从而可以更好地避免或处理异常。
在 Javadoc 添加 @throws 声明,并且描述抛出异常的场景。

public void doSomething(String input) throws MyBusinessException {
     
    ...
}

31.使用描述性消息抛出异常

在抛出异常时,需要尽可能精确地描述问题和相关信息,这样无论是打印到日志中还是在监控工具中,都能够更容易被人阅读,从而可以更好地定位具体错误信息、错误的严重程度等。

但这里并不是说要对错误信息长篇大论,因为本来 Exception 的类名就能够反映错误的原因,因此只需要用一到两句话描述即可。

如果抛出一个特定的异常,它的类名很可能已经描述了这种错误。所以,你不需要提供很多额外的信息。一个很好的例子是 NumberFormatException 。当你以错误的格式提供 String 时,它将被 java.lang.Long 类的构造函数抛出。

try {
     
    new Long("xyz");
} catch (NumberFormatException e) {
     
    log.error(e);
}

32.优先捕获最具体的异常

大多数 IDE 都可以帮助你实现这个最佳实践。当你尝试首先捕获较不具体的异常时,它们会报告无法访问的代码块。

但问题在于,只有匹配异常的第一个 catch 块会被执行。 因此,如果首先捕获 IllegalArgumentException ,则永远不会到达应该处理更具体的 NumberFormatException 的 catch 块,因为它是 IllegalArgumentException 的子类。

总是优先捕获最具体的异常类,并将不太具体的 catch 块添加到列表的末尾。

你可以在下面的代码片断中看到这样一个 try-catch 语句的例子。 第一个 catch 块处理所有 NumberFormatException 异常,第二个处理所有非 NumberFormatException 异常的IllegalArgumentException 异常。

public void catchMostSpecificExceptionFirst() {
     
    try {
     
        doSomething("A message");
    } catch (NumberFormatException e) {
     
        log.error(e);
    } catch (IllegalArgumentException e) {
     
        log.error(e)
    }
}


33.不要捕获 Throwable 类

Throwable 是所有异常和错误的超类。你可以在 catch 子句中使用它,但是你永远不应该这样做!

如果在 catch 子句中使用 Throwable ,它不仅会捕获所有异常,也将捕获所有的错误。JVM 抛出错误,指出不应该由应用程序处理的严重问题。 典型的例子是 OutOfMemoryError 或者 StackOverflowError 。两者都是由应用程序控制之外的情况引起的,无法处理。

所以,最好不要捕获 Throwable ,除非你确定自己处于一种特殊的情况下能够处理错误。

public void doNotCatchThrowable() {
    try {
        
    } catch (Throwable t) {
        
    }
}

34.不要忽略异常

很多时候,开发者很有自信不会抛出异常,因此写了一个catch块,但是没有做任何处理或者记录日志。

public void doNotIgnoreExceptions() {
     
    try {
     
        
    } catch (NumberFormatException e) {
     
        
    }
}

但现实是经常会出现无法预料的异常,或者无法确定这里的代码未来是不是会改动(删除了阻止异常抛出的代码),而此时由于异常被捕获,使得无法拿到足够的错误信息来定位问题。

合理的做法是至少要记录异常的信息。

public void logAnException() {
     
    try {
     
        
    } catch (NumberFormatException e) {
     
        log.error("This should never happen: " + e);
    }
}

35.不要记录并抛出异常

这可能是本文中最常被忽略的最佳实践。可以发现很多代码甚至类库中都会有捕获异常、记录日志并再次抛出的逻辑。如下:

try {
     
    new Long("xyz");
} catch (NumberFormatException e) {
     
    log.error(e);
    throw e;
}

这个处理逻辑看着是合理的。但这经常会给同一个异常输出多条日志。如下:

17:44:28,945 ERROR TestExceptionHandling:65 - java.lang.NumberFormatException: For input string: "xyz"
Exception in thread "main" java.lang.NumberFormatException: For input string: "xyz"
at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
at java.lang.Long.parseLong(Long.java:589)
at java.lang.Long.(Long.java:965)
at com.stackify.example.TestExceptionHandling.logAndThrowException(TestExceptionHandling.java:63)
at com.stackify.example.TestExceptionHandling.main(TestExceptionHandling.java:58)

如上所示,后面的日志也没有附加更有用的信息。如果想要提供更加有用的信息,那么可以将异常包装为自定义异常。

public void wrapException(String input) throws MyBusinessException {
     
    try {
     
        
    } catch (NumberFormatException e) {
     
        throw new MyBusinessException("A message that describes the error.", e);
    }
}

因此,仅仅当想要处理异常时才去捕获,否则只需要在方法签名中声明让调用者去处理。

36.包装异常时不要抛弃原始的异常

捕获标准异常并包装为自定义异常是一个很常见的做法。这样可以添加更为具体的异常信息并能够做针对的异常处理。
在你这样做时,请确保将原始异常设置为原因(注:参考下方代码 NumberFormatException e 中的原始异常 e )。Exception 类提供了特殊的构造函数方法,它接受一个 Throwable 作为参数。否则,你将会丢失堆栈跟踪和原始异常的消息,这将会使分析导致异常的异常事件变得困难。

public void wrapException(String input) throws MyBusinessException {
     
    try {
     
        
    } catch (NumberFormatException e) {
     
        throw new MyBusinessException("A message that describes the error.", e);
    }
}

37.不要使用异常控制程序的流程

不应该使用异常控制应用的执行流程,例如,本应该使用if语句进行条件判断的情况下,你却使用异常处理,这是非常不好的习惯,会严重影响应用的性能。

38.使用标准异常

如果使用内建的异常可以解决问题,就不要定义自己的异常。Java API 提供了上百种针对不同情况的异常类型,在开发中首先尽可能使用 Java API 提供的异常,如果标准的异常不能满足你的要求,这时候创建自己的定制异常。尽可能得使用标准异常有利于新加入的开发者看懂项目代码。

39.异常会影响性能

异常处理的性能成本非常高,每个 Java 程序员在开发时都应牢记这句话。创建一个异常非常慢,抛出一个异常又会消耗1~5ms,当一个异常在应用的多个层级之间传递时,会拖累整个应用的性能。

  • 仅在异常情况下使用异常;
  • 在可恢复的异常情况下使用异常;

尽管使用异常有利于 Java 开发,但是在应用中最好不要捕获太多的调用栈,因为在很多情况下都不需要打印调用栈就知道哪里出错了。因此,异常消息应该提供恰到好处的信息。

40.总结

综上所述,当你抛出或捕获异常的时候,有很多不同的情况需要考虑,而且大部分事情都是为了改善代码的可读性或者 API 的可用性。

异常不仅仅是一个错误控制机制,也是一个通信媒介。因此,为了和同事更好的合作,一个团队必须要制定出一个最佳实践和规则,只有这样,团队成员才能理解这些通用概念,同时在工作中使用它。

41.异常处理-阿里巴巴Java开发手册

1.【强制】Java 类库中定义的可以通过预检查方式规避的RuntimeException异常不应该通过catch 的方式来处理,比如:NullPointerException,IndexOutOfBoundsException等等。 说明:无法通过预检查的异常除外,比如,在解析字符串形式的数字时,可能存在数字格式错误,不得不通过catch NumberFormatException来实现。 正例:if (obj != null) {…} 反例:try { obj.method(); } catch (NullPointerException e) {…}

2.【强制】异常不要用来做流程控制,条件控制。 说明:异常设计的初衷是解决程序运行中的各种意外情况,且异常的处理效率比条件判断方式要低很多。

3.【强制】catch时请分清稳定代码和非稳定代码,稳定代码指的是无论如何不会出错的代码。对于非稳定代码的catch尽可能进行区分异常类型,再做对应的异常处理。 说明:对大段代码进行try-catch,使程序无法根据不同的异常做出正确的应激反应,也不利于定位问题,这是一种不负责任的表现。 正例:用户注册的场景中,如果用户输入非法字符,或用户名称已存在,或用户输入密码过于简单,在程序上作出分门别类的判断,并提示给用户。

4.【强制】捕获异常是为了处理它,不要捕获了却什么都不处理而抛弃之,如果不想处理它,请将该异常抛给它的调用者。最外层的业务使用者,必须处理异常,将其转化为用户可以理解的内容。

5.【强制】有try块放到了事务代码中,catch异常后,如果需要回滚事务,一定要注意手动回滚事务。

6.【强制】finally块必须对资源对象、流对象进行关闭,有异常也要做try-catch。 说明:如果JDK7及以上,可以使用try-with-resources方式。

7.【强制】不要在finally块中使用return。 说明:try块中的return语句执行成功后,并不马上返回,而是继续执行finally块中的语句,如果此处存在return语句,则在此直接返回,无情丢弃掉try块中的返回点。 反例:

private int x = 0;
public int checkReturn() {
     
    try {
     
        
        return ++x;
    } finally {
     
        
        return ++x;
    }
}

8.【强制】捕获异常与抛异常,必须是完全匹配,或者捕获异常是抛异常的父类。 说明:如果预期对方抛的是绣球,实际接到的是铅球,就会产生意外情况。

9.【强制】在调用RPC、二方包、或动态生成类的相关方法时,捕捉异常必须使用Throwable类来进行拦截。 说明:通过反射机制来调用方法,如果找不到方法,抛出NoSuchMethodException。什么情况会抛出NoSuchMethodError呢?二方包在类冲突时,仲裁机制可能导致引入非预期的版本使类的方法签名不匹配,或者在字节码修改框架(比如:ASM)动态创建或修改类时,修改了相应的方法签名。这些情况,即使代码编译期是正确的,但在代码运行期时,会抛出NoSuchMethodError。

10.【推荐】方法的返回值可以为null,不强制返回空集合,或者空对象等,必须添加注释充分说明什么情况下会返回null值。 说明:本手册明确防止NPE是调用者的责任。即使被调用方法返回空集合或者空对象,对调用者来说,也并非高枕无忧,必须考虑到远程调用失败、序列化失败、运行时异常等场景返回null的情况。

11.【推荐】防止NPE,是程序员的基本修养,注意NPE产生的场景: 1) 返回类型为基本数据类型,return包装数据类型的对象时,自动拆箱有可能产生NPE。 反例:public int f() { return Integer对象}, 如果为null,自动解箱抛NPE。 2) 数据库的查询结果可能为null。 3) 集合里的元素即使isNotEmpty,取出的数据元素也可能为null。 4) 远程调用返回对象时,一律要求进行空指针判断,防止NPE。 5) 对于Session中获取的数据,建议进行NPE检查,避免空指针。 6) 级联调用obj.getA().getB().getC();一连串调用,易产生NPE。
正例:使用JDK8的Optional类来防止NPE问题。

12.【推荐】定义时区分unchecked / checked 异常,避免直接抛出new RuntimeException(),更不允许抛出Exception或者Throwable,应使用有业务含义的自定义异常。推荐业界已定义过的自定义异常,如:DAOException / ServiceException等。

13.【参考】对于公司外的http/api开放接口必须使用“错误码”;而应用内部推荐异常抛出;跨应用间RPC调用优先考虑使用Result方式,封装isSuccess()方法、“错误码”、“错误简短信息”。 说明:关于RPC方法返回方式使用Result方式的理由: 1)使用抛异常返回方式,调用方如果没有捕获到就会产生运行时错误。 2)如果不加栈信息,只是new自定义异常,加入自己的理解的error message,对于调用端解决问题的帮助不会太多。如果加了栈信息,在频繁调用出错的情况下,数据序列化和传输的性能损耗也是问题。

14.【参考】避免出现重复的代码(Don’t Repeat Yourself),即DRY原则。 说明:随意复制和粘贴代码,必然会导致代码的重复,在以后需要修改时,需要修改所有的副本,容易遗漏。必要时抽取共性方法,或者抽象公共类,甚至是组件化。 正例:一个类中有多个public方法,都需要进行数行相同的参数校验操作,这个时候请抽取:

private boolean checkParam(DTO dto) {…}

注意:本文归作者所有,未经作者允许,不得转载

全部评论: 0

    我有话说: