We have code similar to the following:
import java.io.Closeable;
public class Test {
@FunctionalInterface
public interface Action1<E extends Exception> {
void run() throws E;
}
public interface Action2 {
void run();
}
static class MyClass implements Action1<Exception>, Action2 {
@Override
public void run() {
}
}
public static <C extends Action1<Exception> & Action2> C emptyAction() {
return (C) new MyClass();
}
public static void main(String[] args) throws Exception {
// Compiler says: "error: incompatible types: MyClass cannot be converted to Closeable"
// Closeable onClose1 = new MyClass();
// onClose1.close();
// Compiles but ClassCastException at runtime:
// Exception in thread "main" java.lang.ClassCastException: class Test$MyClass cannot be cast to class java.io.Closeable (Test$MyClass is in unnamed module of loader // com.sun.tools.javac.launcher.Main$MemoryClassLoader @62fdb4a6; java.io.Closeable is in module java.base of loader 'bootstrap')
// at Test.main(Test.java:27)
Closeable onClose2 = emptyAction();
onClose2.close();
}
}
Notice how directly calling the constructor is correctly rejected by the compiler (onClose1), while the method return type is not rejected, but then proceeds to throw a ClassCastException at runtime (onClose2). I tested with both Java 11 and Java 14 and got the same result both times.
I don't understand why the compiler accepts to compile onClose2? The generic type signature does not indicate that the returned type is indeed Closeable, it is not creating a lambda either which would act as a bridge. I even wondered about erasure, but I don't see which role it could have here...
Does anybody have any explanation?