对 Java 中的 异常 ,做一个微不足道的小小小小记
相同点:
Exception 和Error 都是继承了 Throwable 类,在 Java 中只有 Throwable 类型的实例才可以被抛出或者捕获,它是异常处理机制的基本类型。
不同点:
Exception:是程序正常运行中,可以预料的意外情况,可能并且应该被捕获,进行相应处理。
Exception 又分为 可检查(checked)异常 和 不可检查(unchecked)异常:
一般是指与虚拟机相关的问题,如:系统崩溃、虚拟机错误、内存空间不足、方法调用栈溢出等。这类错误将会导致应用程序中断,仅靠程序本身无法恢复和预防;
类图:
首先,Java 提供了三种可抛出异常(Throwable Exception):
受检查异常(Checked Exception) / 编译期异常 / 非运行时异常:编译时异常在你编译的时候已经发现了有可能的错误情况会强制你添加异常处理,要么用 try … catch… 捕获,要么用 throws 声明抛出,交给父类处理。
运行时异常(Runtime Exception):如:空指针异常、指定的类找不到、数组越界、方法传递参数错误、数据类型转换错误。可以编译通过,但是一运行就停止了,程序不会自己处理;
Throwable:
Error
Exception
Cloneable
接口的实例上调用 Object 的 clone 方法throw:
throws:
实例:
void testException(int a) throws IOException, ...{ // 声明可能抛出的异常
try{
......
}catch(Exception1 e){
throw e; // 抛出异常
}catch(Exception2 e){
System.out.println("出错了!");
}
if(a!=b)
throw new Exception3("自定义异常");
}
由一个问题:主线程可以捕获到子线程的异常吗?而来
线程设计的理念:“线程的问题应该线程自己本身来解决,而不要委托到外部”
正常情况下,如果不做特殊的处理,在主线程中是不能够捕获到子线程中的异常的,如下:
// 定义异常类:
public class ThreadExceptionRunner implements Runnable{
@Override
public void run() {
throw new RuntimeException("RuntimeException!!!");
}
}
// 测试:
public class ThreadExceptionDemo {
public static void main(String[] args) {
// 主线程里去捕捉异常
try {
Thread thread = new Thread(new ThreadExceptionRunner());
thread.start(); // 使用线程执行任务
}catch (Exception e){
System.out.println("===========");
e.printStackTrace();
}
System.out.println("over");
}
}
// 输出: **并未输出"===========",因此未捕获到子线程的异常**
// Exception in thread "Thread-0" java.lang.RuntimeException: RuntimeException!!!
// at cn.xyc.ThreadingTest.ThreadExceptionRunner.run(ThreadExceptionRunner.java:8)
// at java.lang.Thread.run(Thread.java:748)
// over
想要在主线程中捕获子线程的异常:(没有深刻体会…)
方式一:使用 ExecutorService
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadExceptionDemo {
public static void main(String[] args) {
ExecutorService exec = Executors.newCachedThreadPool(new HandleThreadFactory());
exec.execute(new ThreadExceptionRunner());
exec.shutdown();
}
}
import java.util.concurrent.ThreadFactory;
public class HandleThreadFactory implements ThreadFactory {
@Override
public Thread newThread(Runnable r) {
System.out.println("create thread t");
Thread t = new Thread(r);
System.out.println("set uncaughtException for t");
t.setUncaughtExceptionHandler(new MyUncaughtExceptionHandle());
return t;
}
}
public class MyUncaughtExceptionHandle implements Thread.UncaughtExceptionHandler {
@Override
public void uncaughtException(Thread t, Throwable e) {
System.out.println("catch:" + e);
}
}
public class ThreadExceptionRunner implements Runnable{
@Override
public void run() {
throw new RuntimeException("RuntimeException!!!");
}
}
方式二:使用 Thread 的静态方法。
Thread.setDefaultUncaughtExceptionHandler(new MyUncaughtExceptionHandle());
public class ThreadExceptionDemo {
public static void main(String[] args) {
Thread.setDefaultUncaughtExceptionHandler(new MyUncaughtExceptionHandle());
ExecutorService exec = Executors.newCachedThreadPool();
exec.execute(new ThreadExceptionRunner());
exec.shutdown();
}
}
finally 块中的代码什么时候被执行?
在 Java 语言的异常处理中,finally 块的作用就是为了保证无论出现什么情况,finally 块里的代码一定会被执行。
由于程序执行 return 就意味着结束对当前函数的调用并跳出这个函数体,因此任何语句要执行都只能在 return 前执行(除非碰到 exit 函数),因此 finally 块里的代码也是在 return 之前执行的。
此外,如果 try-finally 或者 catch-finally 中都有 return,那么 finally 块中的 return 将会覆盖别处的 return 语句,最终返回到调用者那里的是 finally 中 return 的值。
从字节码角度对其进行说明:
先看下finally在字节码中的体现:
java代码:
public class Demo3_11_4 {
public static void main(String[] args) {
int i = 0;
try {
i = 10;
} catch (Exception e) {
i = 20;
} finally {
i = 30;
}
}
}字节码:
Code:
stack=1, locals=4, args_size=1
0: iconst_0
1: istore_1 // 0 -> i
2: bipush 10 // try --------------------------------------
4: istore_1 // 10 -> i |
5: bipush 30 // finally |
7: istore_1 // 30 -> i |
8: goto 27 // return -----------------------------------
11: astore_2 // catch Exceptin -> e ----------------------
12: bipush 20 // |
14: istore_1 // 20 -> i |
15: bipush 30 // finally |
17: istore_1 // 30 -> i |
18: goto 27 // return -----------------------------------
21: astore_3 // catch any -> slot 3 ----------------------
22: bipush 30 // finally |
24: istore_1 // 30 -> i |
25: aload_3 // <- slot 3 有这个slot3 | 26: athrow // throw 抛出--------------------------------- 27: return Exception table: from to target type 2 5 11 Class java/lang/Exception 2 5 21 any // 剩余的异常类型,比如 Error,监测[2, 5)中的其他异常/error 11 15 21 any // 剩余的异常类型,比如 Error,监测[11, 15)->catch块中的其他异常/error
LocalVariableTable:
Start Length Slot Name Signature
12 3 2 e Ljava/lang/Exception;
0 28 0 args [Ljava/lang/String;
2 26 1 i I可以看到 finally 中的代码被复制了 3 份,分别放入 try 流程,catch 流程以及 catch 剩余的异常类型流程
而当finally代码块中出现了return
java代码
public class Demo3_12_1 {
public static void main(String[] args) {
int result = test();
System.out.println(result); // 20
}public static int test() {
try {
return 10;
} finally {
return 20;
}
}
}字节码:
public static int test();
descriptor: ()I
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=1, locals=2, args_size=0
0: bipush 10 // <- 10 放入栈顶 2: istore_0 // 10 -> slot 0 (把10放入slot0中,并从从栈顶移除)
3: bipush 20 // < - 20 放入栈顶 5: ireturn // 返回栈顶 int(20) 6: astore_1 // catch any -> slot 1
7: bipush 20 // <- 20 放入栈顶
9: ireturn // 返回栈顶 int(20)
Exception table:
from to target type
0 3 6 any由于 finally 中的 ireturn 被插入了所有可能的流程,因此返回结果肯定以 finally 的为准
至于字节码中第 2 行,似乎没啥用,且留个伏笔,看下个例子:
Demo3_12_2
跟上例中的 finally 相比,发现没有 athrow 了,这告诉我们:如果在 finally 中出现了 return,会吞掉异常!可以试一下下面的代码
public class Demo3_12_1 { public static void main(String[] args) { int result = test(); System.out.println(result); // 20 } public static int test() { try { int i = 1/0; // 加不加结果都一样 return 10; } finally { return 20; } } }
很危险的操作,异常被吞掉了,即使发生了异常也不知道了
finally 对返回值影响
java代码
public class Demo3_12_2 {
public static void main(String[] args) {
int result = test();
System.out.println(result); // 10
}public static int test() { int i = 10; try { return i; } finally { i = 20; } }
}
字节码
public static int test();
descriptor: ()I
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=1, locals=3, args_size=0
0: bipush 10 // <- 10 放入栈顶 2: istore_0 // 10 -> i
3: iload_0 // <- i(10) 4: istore_1 // 10 -> slot1,暂存至slot1,目的是为了固定返回值 ☆
5: bipush 20 // <- 20 放入栈顶 7: istore_0 // 20 -> i
8: iload_1 // <- slot 1(10) 载入 slot 1 暂存的值 ☆ 9: ireturn // 返回栈顶的 int(10) 10: astore_2 // 期间出现异常,存储异常到slot2中 11: bipush 20 // <- 20 放入栈顶 13: istore_0 // 20 -> i
14: aload_2 // 异常加载进栈
15: athrow // athrow出异常
Exception table:
from to target type
3 5 10 any参考:
https://www.bilibili.com/video/BV1yE411Z7AP?p=126 ~ https://www.bilibili.com/video/BV1yE411Z7AP?p=128
finally 是不是一定会被执行到?
不一定。下面列举执行不到的情况:
当程序进入 try 块之前就出现异常时,会直接结束,不会执行 finally 块中的代码;
@Test
public void test08(){
int i = 1;
i = i/0;
try {
System.out.println("try");
}finally {
System.out.println("finally");
}
}
当程序在 try 块中强制退出时也不会去执行 finally 块中的代码,比如在 try 块中执行 exit 方法
public void test08(){
int i = 1;
try {
System.out.println("try");
System.exit(0);
}finally {
System.out.println("finally");
}
}
问:try-catch-finally 中,如果 catch 中 return 了,finally 还会执行吗?
会。程序在执行到 return 时会首先将返回值存储在一个指定的位置(上面对finally的字节码中可以体现),其次去执行 finally 块,最后再返回。
因此,对基本数据类型,在 finally 块中改变 return 的值没有任何影响,直接覆盖掉;
// 下述代码返回的结果为1
public static int test(){
String str = "1.1";
int i = 0;
try {
System.out.println("try");
int j = 1/0;
}catch (Exception e){
e.printStackTrace();
System.out.println("catch");
return ++i;
}finally {
System.out.println("finally");
return i;
}
}
而对引用类型是有影响的,返回的是在 finally 对 前面 return 语句返回对象的修改值
// 下述代码返回的结果为def
public static String test02(){
String str = "abc";
try {
System.out.println("try");
int j = 1/0;
}catch (Exception e){
e.printStackTrace();
System.out.println("catch");
return str = "def";
}finally {
System.out.println("finally");
return str;
}
}
问:try-catch-finally 中那个部分可以省略?
catch 可以省略。try 只适合处理运行时异常,try+catch 适合处理运行时异常+普通异常。也就是说,如果你只用 try 去处理普通异常却不加以 catch 处理,编译是通不过的,因为编译器硬性规定,普通异常如果选择捕获,则必须用 catch 显示声明以便进一步处理。而运行时异常在编译时没有如此规定,所以 catch 可以省略,你加上catch 编译器也觉得无可厚非。
@Test
public void test11(){
try {
int i = 1/0;
}finally {
System.out.println("finally");
return;
}
}
finally也可以省略
@Test
public void test11(){
try {
int i = 1/0;
}catch (ArithmeticException e){
e.printStackTrace();
}
}
https://www.cnblogs.com/baxianhua/p/9162103.html
https://mp.weixin.qq.com/s/4E3xRXOVUQzccmP0yahlqA
https://blog.csdn.net/qq_38080370/article/details/87880511
手机扫一扫
移动阅读更方便
你可能感兴趣的文章