java学习--抛出异常

异常的传播

// exception
public class Main {
    public static void main(String[] args) {
        try {
            process1();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    static void process1() {
        process2();
    }

    static void process2() {
        Integer.parseInt(null); // 会抛出NumberFormatException
    }
}
当某个方法抛出了异常时,如果当前方法没有捕获异常,异常就会被抛到上层调用方法,直到遇到某个try ... catch被捕获为止:

main()调用process1();
process1()调用process2();
process2()调用Integer.parseInt(String);
Integer.parseInt(String)调用Integer.parseInt(String, int)。

也叫异常的传播

查看Integer.java源码可知,抛出异常的方法代码如下:

public static int parseInt(String s, int radix) throws NumberFormatException {
    if (s == null) {
        throw new NumberFormatException("null");
    }
    ...
}

异常的抛出

创建某个Exception的实例;
用throw语句抛出。
void process2(String s) {
    if (s==null) {
        NullPointerException e = new NullPointerException();
        throw e;
    }
}


//也就是
void process2(String s) {
    if (s==null) {
        throw new NullPointerException();
    }
}

异常的类型转换

void process1(String s) {
    try {
        process2();
    } catch (NullPointerException e) {
        throw new IllegalArgumentException();
    }
}

void process2(String s) {
    if (s==null) {
        throw new NullPointerException();
    }
}

但是异常类型转换有一个问题就是,先看代码

// exception
//在main()中捕获IllegalArgumentException
public class Main {
    public static void main(String[] args) {
        try {
            process1();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    static void process1() {
        try {
            process2();
        } catch (NullPointerException e) {
            throw new IllegalArgumentException();
        }
    }

    static void process2() {
        throw new NullPointerException();
    }
}
//这个时候打印异常栈我们已经看不到原始异常NullPointerException的信息了。

为了能追踪到完整的异常栈,在构造异常的时候,把原始的Exception实例传进去,新的Exception就可以持有原始Exception信息。对上述代码改进如下:

// exception
public class Main {
    public static void main(String[] args) {
        try {
            process1();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    static void process1() {
        try {
            process2();
        } catch (NullPointerException e) {
            throw new IllegalArgumentException(e);//传入这个e
        }
    }

    static void process2() {
        throw new NullPointerException();
    }
}

捕获到异常并再次抛出时,一定要留住原始异常,否则很难定位第一案发现场!

// exception
public class Main {
    public static void main(String[] args) {
        try {
            Integer.parseInt("abc");
        } catch (Exception e) {
            System.out.println("catched");
            throw new RuntimeException(e);
        } finally {
            System.out.println("finally");
        }
    }
}
///依然会执行finally,jvm会先执行finally再抛出异常

小疑问:

如果在finally里面抛出的异常时前面catch可以接受的异常,那么是不是会一直循环呢?
答案是不会的,而且这样非常蠢,会让程序直接崩掉

//好比自杀的代码
public static void main(String[] args) {
    try {
        System.out.println("1. 执行 try");
        throw new IOException(); // 抛出 A 异常
    } catch (IOException e) {
        System.out.println("2. 捕获到了 try 里的 IOException");
    } finally {
        System.out.println("3. 执行 finally");
        // 故意在 finally 里又跑出一个 IOException
        throw new IOException("Finally 里的异常"); 
    }
}

//catch只会管着try里面的代码,不会管跟他并列的finally


 程序的实际运行路径
执行 try:抛出了 IOException。
执行 catch:匹配成功,打印 "捕获到了..."。
执行 finally:打印 "执行 finally"。
finally 抛出异常:重点来了!
这个异常发生在 finally 块里。
当前的 catch 块已经执行过了,或者说它已经完成了它的使命。
Java 不会“回头”再去检查刚才那个 catch 能不能抓到这个新异常。
结果:这个异常会直接冲破整个 try-catch-finally 结构,抛给给调用它的上层(在上面的例子中,就是抛给 JVM,导致程序崩溃并打印堆栈信息)。

如果一定要在finally里面先抛出一个异常,那么就是再套一层try catch
try {
    try {
        // ... 原本的代码 ...
    } catch (IOException e) {
        // ... 
    } finally {
        throw new IOException(); // 这个异常会抛向外层
    }
} 
catch (IOException e) {
    System.out.println("这里才能抓到 finally 里的异常");
}

异常屏蔽

// exception
public class Main {
    public static void main(String[] args) {
        try {
            Integer.parseInt("abc");
        } catch (Exception e) {
            System.out.println("catched");
            throw new RuntimeException(e);
        } finally {
            System.out.println("finally");
            throw new IllegalArgumentException();
        }
    }
}
//在finally中抛出异常之后,前面抛出的异常就会被忽视掉,这个就是异常屏蔽

完整异常显示

//在极少数的情况下,我们需要获知所有的异常。如何保存所有的异常信息?方法是先用origin变量保存原始异常,然后调用Throwable.addSuppressed(),把原始异常添加进来,最后在finally抛出:

// exception
public class Main {
    public static void main(String[] args) throws Exception {
        Exception origin = null;
        try {
            System.out.println(Integer.parseInt("abc"));
        } catch (Exception e) {
            origin = e;
            throw e;
        } finally {
            Exception e = new IllegalArgumentException();
            if (origin != null) {
                e.addSuppressed(origin);
            }
            throw e;
        }
    }
}
///这个是运行之后可以直接在黑框框上显示异常信息,这里面是自动调用了 printStackTrace(),但是你不可以使用它,你只能看到它,如果想要在代码中使用它,就要用getSuppressed()
public class Main {
    public static void main(String[] args) {
        try {
            // 这里运行你刚才那段会抛出“嵌套异常”的代码
            runDangerousCode(); 
        } catch (Exception e) {
            // 1. 打印主异常(finally里抛出的那个)
            System.out.println("捕获到了主异常: " + e.getClass().getName());

            // 2. 使用 getSuppressed() 获取所有被屏蔽的“附件”
            Throwable[] suppressed = e.getSuppressed();
            //getSuppressed() 返回的是一个数组(Throwable[]),里面装满了所有被屏蔽的异常。
            for (Throwable s : suppressed) {
                // 3. 打印出那些被藏起来的异常(try里抛出的那个)
                System.out.println("发现被屏蔽的附件异常: " + s);
            }
        }
    }

    // 这是刚才那段逻辑
    public static void runDangerousCode() throws Exception {
        Exception origin = null;
        try {
            Integer.parseInt("abc"); // 抛出 NumberFormatException
        } catch (Exception e) {
            origin = e;
            throw e;
        } finally {
            Exception e = new IllegalArgumentException(); // 主异常
            if (origin != null) {
                e.addSuppressed(origin); // 把刚才的报错塞进兜里
            }
            throw e; 
        }
    }
}
posted @ 2026-05-12 19:11  Time_q  阅读(21)  评论(0)    收藏  举报