为何CompletableFuture.complete()传入错误类型时不抛出ClassCastException?
咱们先看你给出的代码:
CompletableFuture<Integer> publicStage = new CompletableFuture<>(); publicStage.thenAccept(i -> System.out.println("i = " + i)); CompletableFuture<String> cfs = (CompletableFuture)publicStage; cfs.complete("foo");
这个现象的核心涉及两个关键点:Java泛型擦除和CompletableFuture的异常处理逻辑,我来一步步拆解解释:
1. 泛型擦除导致强转不报错
Java的泛型是编译时的语法糖,运行时所有泛型参数都会被擦除。所以你把CompletableFuture<Integer>强制转换成CompletableFuture<String>时,JVM在运行时根本感知不到两者的泛型差异——它只看到这是一个CompletableFuture实例,因此这个强转不会抛出ClassCastException。
2. 回调执行时的异常被默默丢弃
当你调用cfs.complete("foo"),本质是给同一个publicStage实例设置了一个String类型的结果。而你之前通过thenAccept注册的回调,期望接收的是Integer类型参数。
当CompletableFuture尝试执行这个回调时,JVM会尝试把String对象强制转型为Integer,这时候确实会抛出ClassCastException——但你看不到这个错误,是因为CompletableFuture的内部执行机制会捕获所有未处理的回调异常,默认情况下不会把异常传播到主线程,也不会打印到控制台,这些异常就被默默丢弃了。
如何验证这个异常?
你可以通过exceptionally()方法主动捕获回调中的异常,修改代码如下:
CompletableFuture<Integer> publicStage = new CompletableFuture<>(); publicStage.thenAccept(i -> System.out.println("i = " + i)) .exceptionally(e -> { System.err.println("回调执行出错:" + e.getMessage()); return null; }); CompletableFuture<String> cfs = (CompletableFuture)publicStage; cfs.complete("foo");
运行这段代码,你就能看到控制台输出:
回调执行出错:java.lang.ClassCastException: class java.lang.String cannot be cast to class java.lang.Integer
正确的类型转换方式
这种不安全的泛型强转是典型的代码坏味道,应该避免。如果你需要转换CompletableFuture的结果类型,建议使用thenApply()这类安全的方法:
CompletableFuture<Integer> publicStage = new CompletableFuture<>(); // 安全转换Integer到String CompletableFuture<String> cfs = publicStage.thenApply(String::valueOf); publicStage.complete(123); cfs.thenAccept(s -> System.out.println("s = " + s)); // 输出:s = 123
内容的提问来源于stack exchange,提问作者Alexander Kjäll

