使用具有多个边界的泛型时,编译器不会打印编译时错误

时间:2015-04-10 18:08:47

标签: java generics compiler-errors

我试图理解为什么编译器不会在下面的代码中打印编译时错误。它编译,但显然不会工作。

有人知道编译器允许它的原因吗?

public class Tests {
    public static void main(String... args){
        // Lines below are acceptable for the compiler and work well in runtime.
        GenericClass<FooClassWithFooInterface> genericClass1 = new GenericClass();
        genericClass1.print(new FooClassWithFooInterface());

        // Lines below are oddly acceptable for the compiler and, obviously, won't work in runtime.
        GenericClass genericClass2 = new GenericClass();
        genericClass2.print(new FooClassWithFooInterface());
        genericClass2.print(new FooClass()); // why the compiler not throw a compile-time error?
    }
}


class GenericClass<T extends FooClass & FooInterface>{
    public void print(T t){
        t.fooMethod();
    }
}

class FooClass{

}

interface FooInterface{
    public void fooMethod();
}

class FooClassWithFooInterface extends FooClass implements FooInterface{
    @Override
    public void fooMethod() {
        System.out.println("foo");
    }   
}

控制台输出:

foo
foo
Exception in thread "main" java.lang.ClassCastException: FooClass cannot be cast to FooInterface
    at GenericClass.print(Tests.java:18)
    at Tests.main(Tests.java:11)

我创建了fooMethod()只是为了强制这个运行时错误。

我认为编译器可以检查new FooClass()是否与<? extends FooClass & FooInterface>不匹配并强制编译错误。

如果我们将GenericClass更改为T extends FooClassWithFooInterface而不是T extends FooClass & FooInterface,编译器最终会显示编译时错误:

class GenericClass<T extends FooClassWithFooInterface>{
    public void print(T t){
        t.fooMethod();
    }
}

此外,我在Restrictions on Generics(The Java Tutorial)

中未发现与此问题相关的任何限制

2 个答案:

答案 0 :(得分:0)

当您使用多个边界时,在编译时类型擦除之后,第一个边界将保留在类型签名中。根据需要为后续边界插入强制转换。所以,如果您要查看已编译的GenericClass,您会看到类似

的内容
class GenericClass {
    public void print(FooClass t){
        ((FooInterface) t).fooMethod();
    }
}

因为编译器发现GenericClass具有print(FooClass)方法,所以它不会抱怨。但是在运行时,方法内部的强制转换失败了。

为什么编译器允许这样做,当一个人可以推断这必然会失败?好吧,编译器并不像你那么聪明。当你将自己局限于类型安全代码时,它只能找到问题,这意味着永远不要使用原始类型或抑制类型警告。

还有一些其他情况,一个人,看着上下文,可以推断其他操作是安全的,但编译器会抱怨。编译器只使用声明的信息,一次查看一个表达式;它不考虑泛型类型的整个上下文。

答案 1 :(得分:0)

您明确告诉编译器在此处检查类型:

GenericClass genericClass2 = new GenericClass();

通过省略泛型类型参数,您将强制编译器进入遗留模式(其中为java prioer编写的代码使用JDK5 +进行5次编译)。如果没有给出泛型类型参数,则会得到警告,并且编译器接受泛型类型的任何类型。

所以基本上你是在拍摄自己的脚,现在抱怨编译器没有阻止你,明确告诉他闭嘴后:)