2023年11月29日发(作者:)

Java异常题⽬总结

问:java 异常有哪⼏种,特点是什么?

答:异常是发⽣在程序执⾏过程中阻碍程序正常执⾏的错误操作,只要在 Java 语句执⾏中产⽣异常则⼀个异常对象就会被创建。

Throwable 是所有异常的⽗类,它有两个直接⼦类 Error 和 Exception,其中 Exception ⼜被继续划分为被检查的异常(checked

exception)和运⾏时的异常(runtime exception,即不受检查的异常);Error 表⽰系统错误,通常不能预期和恢复(譬如 JVM 崩

溃、内存不⾜等);被检查的异常(Checked exception)在程序中能预期且要尝试修复(如我们必须捕获 FileNotFoundException 异

常并为⽤户提供有⽤信息和合适⽇志来进⾏调试,Exception 是所有被检查的异常的⽗类);运⾏时异常(Runtime Exception)⼜称为

不受检查异常,譬如我们检索数组元素之前必须确认数组的长度,否则就可能会抛出 ArrayIndexOutOfBoundException 运⾏时异

常,RuntimeException 是所有运⾏时异常的⽗类。

如下图IO异常和SQL异常都是检查异常

问:java throw throws 的区别是什么?

答:throw 使⽤的位置在⽅法中,后⾯跟的异常对象实例,表⽰抛出异常,由⽅法体内语句处理,如果⽅法中有 throw 抛出

RuntimeException 及其⼦类则声明上可以没有 throws,如果⽅法中有 throw 抛出 Exception 及其⼦类则声明上必须有 throws。

throws 使⽤的位置在⽅法参数⼩括号后⾯,后⾯跟的是⼀个或者多个异常类名且⽤逗号隔开,表⽰抛出异常并交给调⽤者去处理,如果后

⾯根据的是 RuntimeException 及其⼦类则该⽅法可以不⽤处理,如果后⾯根据的是 Exception 及其⼦类则必须要编写代码进⾏处理或者

调⽤的时候抛出。

throw是语句抛出⼀个异常。

语法:

throw (异常对象);

throw e;

throws是⽅法可能抛出异常的声明。(⽤在声明⽅法时,表⽰该⽅法可能要抛出异常)

语法:

[(修饰符)](返回值类型)(⽅法名)([参数列表])[throws(异常类)]{......}

public void doA(int a) throws Exception1,Exception3{......}

/*

举例:

throws E1,E2,E3

只是告诉程序这个⽅法可能会抛出这些异常,

⽅法的调⽤者可能要处理这些异常,⽽这些异常可能是该函数体产⽣的。

E1E2E3

throw*/

则是明确了这个地⽅要抛出这个异常。

void doA(int a) throws IOException,{

try{

......

}catch(Exception1 e){

throw e;

}catch(Exception2 e){

System.out.println("出错了!");

}

if(a!=b)

throw new Exception3("⾃定义异常");

}

代码块中可能会产⽣3个异常,(Exception1,Exception2,Exception3)。

如果产⽣Exception1异常,则捕获之后再抛出,由该⽅法的调⽤者去处理。

如果产⽣Exception2异常,则该⽅法⾃⼰处理了(即n(“出错了!”);)。所以该⽅法就不会再向外抛出Exception2异

常了,void doA() throws Exception1,Exception3 ⾥⾯的Exception2也就不⽤写了。

⽽Exception3异常是该⽅法的某段逻辑出错,程序员⾃⼰做了处理,在该段逻辑错误的情况下抛出异常Exception3,则该⽅法的调⽤者也

要处理此异常。

问:java 中被检查的异常和不受检查的异常有什么区别?

答:被检查的异常应该⽤ try-catch 块代码处理或⽤ throws 关键字抛出,不受检查的异常在程序中不要求被处理或⽤ throws 抛出;

Exception 是所有被检查异常的基类,⽽ RuntimeException(是 Exception 的⼦类) 是所有不受检查异常的基类;被检查的异常适⽤于

那些不是因程序引起的错误情况(如 FileNotFoundException),⽽不被检查的异常通常都是由于糟糕的编程引起(如

NullPointerException)。

问:java Error Exception 有什么区别?

答:Error 表⽰系统级的错误,是 java 运⾏环境内部错误或者硬件问题,不能指望程序来处理这样的问题,除了退出运⾏外别⽆选择,它

是 java 虚拟机抛出的。Exception 表⽰程序需要捕捉、需要处理的异常,是由与程序设计的不完善⽽出现的问题,程序可以处理的问题。

问:java 中什么是异常链?

答:异常链是指在进⾏⼀个异常处理时抛出了另外⼀个异常,由此产⽣了⼀个异常链条,⼤多⽤于将受检查异常(checked exception)封

装成为⾮受检查异常(unchecked exception)或者 RuntimeException。特别注意如果你因为⼀个异常⽽决定抛出另⼀个新的异常时⼀定

要包含原有的异常,这样处理程序才可以通过 getCause() 和 initCause() ⽅法来访问异常最终的根源。

问:java 中如何编写⾃定义异常?

答:可以通过继承 Exception 类或其任何⼦类来实现⾃⼰的⾃定义异常类,⾃定义异常类可以有⾃⼰的变量和⽅法来传递错误代码或其它

异常相关信息来处理异常。下⾯是⼀个⾃定义异常的常见模板:

public class DemoException extends IOException

{

private static final long serialVersionUID = 123456789L;

private String errorCode= "DemoException";

public DemoException(String msg, String errorCode){

super(msg);

this.errorCode = errorCode;

}

public String getErrorCode(){

return this.errorCode;

}

}

问:请简单描述下⾯⽅法的执⾏流程和最终返回值是多少?

public static int test1(){

int ret = 0;

try {

return ret;

} finally {

ret = 2;

}

}

public static int test2(){

int ret = 0;

try {

int a = 5 / 0;

return ret;

} finally {

return 2;

}

}

public static void test3(){

try {

int a = 5 / 0;

} finally {

throw new RuntimeException("hello");

}

}

答:本题旨在考察 try-catch-finally 块的⽤法踩坑经验,具体解析如下。

test1 ⽅法运⾏返回 0,因为执⾏到 try 的 return ret; 语句前会先将返回值 ret 保存在⼀个临时变量中,然后才执⾏ finally 语句,最后

try 再返回那个临时变量,finally 中对 ret 的修改不会被返回。

test2 ⽅法运⾏返回 2,因为 5/0 会触发 ArithmeticException 异常,但是 finally 中有 return 语句,finally 中 return 不仅会覆盖 try

和 catch 内的返回值且还会掩盖 try 和 catch 内的异常,就像异常没有发⽣⼀样(特别注意,当 finally 中没有 return 时该⽅法运⾏会抛

出 ArithmeticException 异常),所以这个⽅法就会返回 2,⽽且不再向上传递异常了。

test3 ⽅法运⾏抛出 hello 异常,因为如果 finally 中抛出了异常,则原异常就会被掩盖。

因此为避免代码逻辑混淆,我们应该避免在 finally 中使⽤ return 语句或者抛出异常,如果调⽤的其他代码可能抛出异常,则应该捕获异常并进⾏处理。

问:如果执⾏ finally 代码块之前⽅法返回了结果或者 JVM 退出了,这时 finally 块中的代码还会执⾏吗?

答:只有在 try ⾥⾯通过 (0) 来退出 JVM 的情况下 finally 块中的代码才不会执⾏,其他 return 等情况都会调⽤,所以在不

终⽌ JVM 的情况下 finally 中的代码⼀定会执⾏。

问:分别说说下⾯代码⽚段都有什么问题?

public static void func() throws RuntimeException, NullPointerException {

throw new RuntimeException("func exception");

}

public static void main(String args[]) {

try {func();

} catch (Exception ex) {

ex.printStackTrace();

} catch (RuntimeException re) {

re.printStackTrace();

}

}

上⾯代码段对于 func ⽅法后⾯ throws 列出的异常类型是不分先后顺序的,所以 func ⽅法是没问题的;对于 main ⽅法中在捕获

RuntimeException 类型变量 re 的地⽅会编译错误,因为 Exception 是 RuntimeException 的超类,func ⽅法执⾏的异常都会被第⼀

个 catch 块捕获,所以会报编译时错误。

public class Base {

public void func() throws IOException {

throw new IOException("Base IOException");

}

}

public class Sub extends Base {

public void func() throws Exception {

throw new Exception("Sub Exception");

}

}

如上代码⽚段在编译时⼦类 func ⽅法会出现编译异常,因为在 java 中重写⽅法抛出的异常不能是原⽅法抛出异常的⽗类,这⾥ func ⽅

法在⽗类中抛出了 IOException,所有在⼦类中的 func ⽅法只能抛出 IOExcepition 或是其⼦类,但不能是其⽗类。

public static void func() {}

public static void main(String args[]) {

try {

func();

} catch (IOException e) {

e.printStackTrace();

}

}

上⾯代码段编译时在 IOException 时会出现编译错误,因为 IOException 是受检查异常,⽽ func ⽅法并没有抛出 IOException,所以编

译报错,但是如果将 IOException 改为 Exception(或者 NullPointerException 等)则编译报错将消失,因为 Exception 可以⽤来捕捉

所有运⾏时异常,这样就不需要声明抛出语句。

答:

通过上⾯⼏个代码⽚段可以看出我们在书写多 catch 块时要保证异常类型的优先级书写顺序,要保证⼦类靠前⽗类靠后的原则;此外在 java 中重写⽅法抛

出的异常不能是原⽅法抛出异常的⽗类;如果⽅法没有抛出受检查类型异常则在调⽤⽅法的地⽅就不能主动添加受检查类型异常捕获,但是可以添加运⾏时异常

或者 Exception 捕获。

问:关于 java 中的异常处理你有啥⼼得或者经验?

答:这其实是⼀个经验题,答案不局限的,可以⾃由发挥,下⾯给出⼏个⽰例点。

⽅法返回值尽量不要使⽤ null(特殊场景除外),这样可以避免很多 NullPointerException 异常。

catch 住了如果真的没必要处理则⾄少加⾏打印,这样可在将来⽅便排查问题。

接⼝⽅法抛出的异常尽量保证是运⾏时异常类型,除⾮迫不得已才抛出检查类型异常。

避免在 finally 中使⽤ return 语句或者抛出异常,如果调⽤的其他代码可能抛出异常则应该捕获异常并进⾏处理,因为 finally 中

return 不仅会覆盖 try 和 catch 内的返回值且还会掩盖 try 和 catch 内的异常,就像异常没有发⽣⼀样(特别注意,当 try-finally

中没有 return 时该⽅法运⾏会继续抛出异常)。

尽量不要在 catch 块中压制异常(即什么也不处理直接 return),因为这样以后⽆论抛出什么异常都会被忽略,以⾄没有留下任何问

题线索,如果在这⼀层不知道如何处理异常最好将异常重新抛出由上层决定如何处理异常。