绕过 Java 编译器检查,在任何地方抛出受检异常

刺骨的言语ヽ痛彻心扉 2023-06-07 15:26 88阅读 0赞

1 Java 异常简介

  1. 众所周知,Java 的所有异常都派生自 Throwable 类,在继承结构上,从 Throwable 派生出了 Error Exception 两大类。其中,Error 表示系统级别的严重程序错误,一般由 JVM 抛出,我们也不应该捕获这类异常,用户自定义的异常一般都派生自 Exception 类。
  2. 从是否被编译器强制检查一点,异常又可分为受检异常(Checked Exception)和未受检异常(Unchecked Exception)。未受检异常派生自 Error 或者 RuntimeException,表示不可恢复的程序错误,典型例子有 AssertionErrorNullPointerException 等,编译器不会强制我们捕获这类异常。受检异常则是除了 Error/RuntimeException 之外,派生自 Throwable 或者 Exception 的其他异常,比如 IOExceptionSQLException 等。

如果一个方法声明自己可能抛出受检异常,那么编译器会强制它的调用者必须使用 try-catch 捕获此异常,或者在自己的方法中加上 throws 声明将异常继续传播给外界除非抛出RuntimeException或其子类,可在外部进行异常捕获。

20191015162514440.jpg

  1. 多年以来,Java 中受检异常的设计一直颇受争议,反对者认为,受检异常容易破坏方法声明的兼容性,会使代码的可读性降低,还增加开发的工作量等等。当然也有一些支持者,他们认为受检异常可以强迫程序员去思考,有助于他们写出更健壮的代码,可以参考王垠的文章「[Kotlin Checked Exception][Kotlin _ Checked Exception]」。

2 java 中的异常处理实例

直接上代码,先贴下面测试需要调用的方法:
// catch 后续处理工作
public static boolean catchMethod() {
System.out.print(“call catchMethod and return —->> “);
return false;
}
// finally后续处理工作
public static void finallyMethod() {
System.out.println();
System.out.print(“call finallyMethod and do something —->> “);
}

1. 抛出 Exception,没有 finally,当 catch 遇上 return

public static boolean catchTest() {
try {
int i = 10 / 0; // 抛出 Exception,后续处理被拒绝
System.out.println(“i vaule is : “ + i);
return true; // Exception 已经抛出,没有获得被执行的机会
} catch (Exception e) {
System.out.println(“ — Exception —“);
return catchMethod(); // Exception 抛出,获得了调用方法并返回方法值的机会
}
}
后台输出结果:
-- Exception —
call catchMethod and return —->> false

2. 抛出 Exception,当 catch 体里有 return,finally 体的代码块将在 catch 执行 return 之前被执行
public static boolean catchFinallyTest1() {
try {
int i = 10 / 0; // 抛出 Exception,后续处理被拒绝
System.out.println(“i vaule is : “ + i);
return true; // Exception 已经抛出,没有获得被执行的机会
} catch (Exception e) {
System.out.println(“ — Exception —“);
return catchMethod(); // Exception 抛出,获得了调用方法的机会,但方法值在 finally 执行完后才返回
}finally{
finallyMethod(); // Exception 抛出,finally 代码块将在 catch 执行 return 之前被执行
}
}
后台输出结果:

— Exception —
call catchMethod and return —->>
call finallyMethod and do something —->> false

3. 不抛 Exception,当 finally 代码块里面遇上 return,finally 执行完后将结束整个方法

public static boolean catchFinallyTest2() {
try {
int i = 10 / 2; // 不抛出 Exception
System.out.println(“i vaule is : “ + i);
return true; // 获得被执行的机会,但执行需要在 finally 执行完成之后才能被执行
} catch (Exception e) {
System.out.println(“ — Exception —“);
return catchMethod();
}finally{
finallyMethod();
return false; // finally 中含有 return 语句,这个 return 将结束这个方法,不会在执行完之后再跳回 try 或 catch 继续执行,方法到此结束,返回 false
}
}

后台输出结果:
i vaule is : 5
call finallyMethod and do something —->> false

4. 不抛 Exception,当 finally 代码块里面遇上 System.exit() 方法 将结束和终止整个程序,而不只是方法

public static boolean finallyExitTest() {
try {
int i = 10 / 2; // 不抛出 Exception
System.out.println(“i vaule is : “ + i);
return true; // 获得被执行的机会,但由于 finally 已经终止程序,返回值没有机会被返回
} catch (Exception e) {
System.out.println(“ — Exception —“);
return true;
}finally {
finallyMethod();
System.exit(0);// finally 中含有 System.exit() 语句,System.exit() 将退出整个程序,程序将被终止
}
}

后台输出结果:
i vaule is : 5
call finallyMethod and do something —->>

5. 抛出 Exception,当 catch 和 finally 同时遇上 return,catch 的 return 返回值将不会被返回,finally 的 return 语句将结束整个方法并返回

public static boolean finallyTest1() {
try {
int i = 10 / 0; // 抛出 Exception,后续处理被拒绝
System.out.println(“i vaule is : “ + i);
return true; // Exception 已经抛出,没有获得被执行的机会
} catch (Exception e) {
System.out.println(“ — Exception —“);
return true; // Exception 已经抛出,获得被执行的机会,但返回操作将被 finally 截断
}finally {
finallyMethod();
return false; // return 将结束整个方法,返回 false
}
}

后台输出结果:
— Exception —
call finallyMethod and do something —->> false

6. 不抛出 Exception,当 finally 遇上 return,try 的 return 返回值将不会被返回,finally 的 return 语句将结束整个方法并返回

public static boolean finallyTest2() {
try {
int i = 10 / 2; // 不抛出 Exception
System.out.println(“i vaule is : “ + i);
return true; // 获得被执行的机会,但返回将被 finally 截断
} catch (Exception e) {
System.out.println(“ — Exception —“);
return true;
}finally {
finallyMethod();
return false; // return 将结束这个方法,不会在执行完之后再跳回 try 或 catch 继续执行,返回 false
}
}
后台输出结果:
i vaule is : 5
call finallyMethod and do something —->> false

结语:

java 的异常处理中(假设方法需要返回值):

  1. 在不抛出异常的情况下,程序执行完 try 里面的代码块之后,该方法并不会立即结束,而是继续试图去寻找该方法有没有 finally 的代码块,
  2. 如果没有 finally 代码块,整个方法在执行完 try 代码块后返回相应的值来结束整个方法;
  3. 如果有 finally 代码块,此时程序执行到 try 代码块里的 return 语句之时并不会立即执行 return,而是先去执行 finally 代码块里的代码,
  4. 若 finally 代码块里没有 return 或没有能够终止程序的代码,程序将在执行完 finally 代码块代码之后再返回 try 代码块执行 return 语句来结束整个方法;
  5. 若 finally 代码块里有 return 或含有能够终止程序的代码,方法将在执行完 finally 之后被结束,不再跳回 try 代码块执行 return。

在抛出异常的情况下,原理也是和上面的一样的,你把上面说到的 try 换成 catch 去理解就 OK 了 *_*

###################

java异常处理-finally中使用return和throw语句

java异常语句中的finally块通常用来做资源释放操作,如关闭文件、关闭网络连接、关闭数据库连接等。正常情况下finally语句中不应该使用return语句也不应该抛出异常,以下讨论仅限于java语言设计本身,正常编码时应避免。

finally块中使用return会覆盖method的返回值

以下代码的返回值为:1

  1. public static int div(){
  2. try {
  3. return 3;
  4. }catch (ArithmeticException e){
  5. System.out.println("catch in div");
  6. return 2;
  7. }
  8. finally {
  9. System.out.println("finally in div");
  10. return 1;
  11. }
  12. }

以下代码的返回值同样是:1

  1. public static int div(){
  2. try {
  3. return 3/0;
  4. }catch (ArithmeticException e){
  5. System.out.println("catch in div");
  6. return 2;
  7. }
  8. finally {
  9. System.out.println("finally in div");
  10. return 1;
  11. }
  12. }

finally块中使用return会抑制异常的冒泡传输

即:只要finally中使用了return语句,调用者便认为该方法正常返回

以下代码

  1. /**
  2. * Created by Administrator on 2017/11/27.
  3. */
  4. public class Test {
  5. public static void main(String[] args) {
  6. adapter();
  7. }
  8. public static void adapter() {
  9. try {
  10. div();
  11. } catch (ArithmeticException e) {
  12. System.out.println("catch in adapter");
  13. } finally {
  14. System.out.println("finally in adapter");
  15. }
  16. }
  17. public static int div() {
  18. try {
  19. int a = 5 / 0;
  20. return a;
  21. } catch (ArithmeticException e) {
  22. System.out.println("catch in div");
  23. throw e; // 重新将异常抛出给调用者
  24. } finally {
  25. System.out.println("finally in div");
  26. }
  27. }
  28. }

输出为

catch in div
finally in div
catch in adapter
finally in adapter

但如果在 div 的finally块中添加了return语句

  1. public static int div(){
  2. try {
  3. int a = 5/0;
  4. return a;
  5. }catch (ArithmeticException e){
  6. System.out.println("catch in div");
  7. throw e; // 重新将异常抛出给调用者,但是抛出会被忽略
  8. }
  9. finally {
  10. System.out.println("finally in div");
  11. return 1;
  12. }
  13. }

则代码的输出为

catch in div
finally in div
finally in adapter

即:finally块中的return语句会阻止异常的栈调用传输,使caller认为该方法已经正常返回

finally块中的throw语句会覆盖try和catch语句中的异常

以下代码

  1. /**
  2. * Created by Administrator on 2017/11/27.
  3. */
  4. public class Test {
  5. public static void main(String[] args) {
  6. adapter();
  7. }
  8. public static void adapter() {
  9. try {
  10. div();
  11. } catch (Exception e) {
  12. System.out.println(String.format("catch in adapter: %s",e.getMessage()));
  13. } finally {
  14. System.out.println("finally in adapter");
  15. }
  16. }
  17. public static int div() throws Exception{
  18. try {
  19. int a = 5 / 0;
  20. return a;
  21. } catch (ArithmeticException e) {
  22. System.out.println("catch in div");
  23. throw new Exception("Exception in div"); // 抛出新的异常
  24. } finally {
  25. System.out.println("finally in div");
  26. throw new Exception("Exception in Finally"); // 抛出新的异常
  27. }
  28. }
  29. }

输出是:

catch in div
finally in div
catch in adapter: Exception in Finally
finally in adapter

即,catch块中抛出的异常北finally块抛出的异常替换了

修改div方法为

  1. public static int div() throws Exception{
  2. try {
  3. int a = 5 / 0;
  4. return a;
  5. } finally {
  6. System.out.println("finally in div");
  7. throw new Exception("Exception in Finally"); // 抛出新的异常
  8. }
  9. }

输出为:

finally in div
catch in adapter: Exception in Finally
finally in adapter

即,try块中捕获的异常北finally块抛出的异常替换

finally块和普通代码块一样,无法同时使用return语句和throw语句,因为无法通过编译

发表评论

表情:
评论列表 (有 0 条评论,88人围观)

还没有评论,来说两句吧...

相关阅读

    相关 如何Java异常

    在Java中,当你想要程序因为某种错误状态而停止执行时,可以使用`throw`关键字来抛出一个异常。 以下是一个简单的例子,我们创建一个方法,如果输入的数字不是正数,就抛出一

    相关 异常和非异常

    `受检异常`主要指编译时强制检查的异常,包括非受检异常之外的其他 Throwable 的子类; `非受检异常`主要指编译器免检异常,通常包括运行时异常类和 Error相关类