当`T extends Comparable <! - 时,为什么`Class <t> == Boolean.class`导致编译器错误?超级T - >`?

时间:2015-07-08 15:05:41

标签: java generics java-7 comparable

我正在使用泛型来抽象出可比数据类型,如下面提供的代码所示。这种情况来自Java Swing组件,特别是尝试使用表模型来使用泛型。

我有一个解决这个问题的工作方案(下面的案例3A)。但是,在我到达该解决方案的过程中,当我将类签名从T extends Comparable<T>更改为T extends Comparable<? super T>时,我对编译器的行为感到困惑。

为什么编译器希望Comparable是原始类型(下面的案例3A)?我希望类签名能够工作(案例6A,Class<T extends Comparable<? super T>>),但它需要强制转换,并在使用if (type == Boolean.class)时导致编译错误。

为什么案例2B,3A / B和4A / B允许if (type == Boolean.class)但案例1A / B,2A,5A / B和6A / B会导致编译错误?具体来说,我想在2A和2B的情况下解释这个错误,它们共享相同的类签名。

可运行的测试用例如下。提供了外部ScoreDetailedScore类来演示涉及继承的案例,这突出了案例A和案例B之间的区别。

ComparableTest.java

public class ComparableTest<L, T extends Comparable<? super T>> // Case A
//public class ComparableTest<L, T extends Comparable<T>> // Case B: Works when used without inheritance.
{
    public static void main(String[] args)
    {
        new ComparableTest<String, Boolean>(new String("B"), Boolean.TRUE);
        new ComparableTest<String, Float>(new String("F"), new Float(1f));
        new ComparableTest<String, Score>(new String("S"), new Score(5f));
        new ComparableTest<String, Score>(new String("D"), new DetailedScore<String>("DS.S", 5f));
        new ComparableTest<String, DetailedScore<?>>(new String("DS"), new DetailedScore<String>("DS.DS", 5f));
    }

    public ComparableTest(L label, T value)
    {
        // Case 1A: Compiler Error: Type mismatch: cannot convert from Class<capture#2-of ? extends Comparable> to Class<T>
//        Class<T> type = value.getClass(); // Q: Why can't I use Class<T>?

        // Case 2A: Compiler Warning: Type safety: Unchecked cast from Class<capture#2-of ? extends Comparable> to Class<T>
//        Class<T> type = (Class<T>) value.getClass(); // Case 2B: This works if 'T extends Comparable<T>' (see note in class declaration above).

        // Case 3A: Compiler Warning: Comparable is a raw type. References to generic type Comparable<T> should be parameterized
        Class<? extends Comparable> type = value.getClass(); // Q: Why must Comparable be a raw type here?

        // Case 4A: Compiler Warning: Type safety: Unchecked cast from Class<capture#2-of ? extends Comparable> to Class<? extends Comparable<?>>
//        Class<? extends Comparable<?>> type = (Class<? extends Comparable<?>>) value.getClass();

        // Case 5A: Compiler Warning: Type safety: Unchecked cast from Class<capture#2-of ? extends Comparable> to Class<? extends Comparable<T>>
//        Class<? extends Comparable<T>> type = (Class<? extends Comparable<T>>) value.getClass();

        // Case 6A: Compiler Warning: Type safety: Unchecked cast from Class<capture#2-of ? extends Comparable> to Class<? extends Comparable<? super T>>
//        Class<? extends Comparable<? super T>> type = (Class<? extends Comparable<? super T>>) value.getClass();

        // Case 1A, 2A: Compiler Error: Incompatible operand types Class<T> and Class<Boolean>
        // Case 2B, 3A/B, 4A/B: OK.
        // Case 5A/B, 6A/B: Compiler Error: Incompatible operand types Class<capture#4-of ? extends Comparable<T>> and Class<Boolean>
        if (type == Boolean.class)
        {
            System.out.println("Treating " + label + " as boolean (" + type.getCanonicalName() + ")");
        } else if (type == Float.class) {
            System.out.println("Treating " + label + " as float (" + type.getCanonicalName() + ")");
        } else {
            System.out.println("Treating " + label + " as (" + type.getCanonicalName() + ")");
        }

        return;
    }
}

Score.java

public class Score implements Comparable<Score>
{
    private Float value;

    public Score(Float value)
    {
        this.value = value;
        return;
    }

    @Override
    public int compareTo(Score o)
    {
        return this.value.compareTo(o.value); // for brevity
    }
}

DetailedScore.java

public class DetailedScore<D> extends Score
{
    private D detail;

    public DetailedScore(D someDetail, Float value)
    {
        super(value);
        this.detail = someDetail;
        return;
    }

    public D getDetail()
    {
        return this.detail;
    }
}

2 个答案:

答案 0 :(得分:3)

Compiler warnings are not the same as errors... for example, your "case 2A" will compile... it will generate a warning but will give you a usable class file. These are generally due to erasure, which is a complicated topic that I don't pretend to fully understand.

But for example, when T extends Comparable<? super T>, if you have value which is declared as a T, of course you should be able to cast value.getClass() to Class<T>. But due to erasuer the compiler only remembers that value.getClass() should return a Class<? extends Comparable>, so it warns you that you should be careful when casting to Class<T>. You can shut up the warning, if you're sure about things, by using the @SuppressWarnings annotation, like this:

    @SuppressWarnings("unchecked")
    Class<T> type = (Class<T>)value.getClass();

A good IDE (like Eclipse) will suggest this annotation as a possibility to fix the warning; if you aren't using a good IDE I'd suggest you start.

So let's talk about the actual errors instead. In Java you can only check for object equality if two things are the same type or are assignable to the same type. For example, this works:

public static class Foo {

}

public static class Bar extends Foo {

}

public static void main(String...args) {
    Foo f = new Foo();
    Bar b = new Bar();

    if (f == b) {

    }
}

because, at the point of comparison, f could be a Bar under the covers; it's allowed. On the other hand, this does not work:

    Boolean b = Boolean.FALSE;
    Integer i = 5;

    if (b == i) {  // ERROR

    }

There is no way these two could ever be equal. It's an error.

From the compiler's point of view, Class<T> and Class<Boolean> (which is the type of Boolean.class, natch) are different types. If T extends Comparable<? super T>, they are not assignable to each other, since Boolean implements Comparable<Boolean>; it does not implement Comparable<? super Boolean>. So you cannot check for equality with ==.

(If T extends Comparable<T> then that is compatible with Boolean, and you can do that comparison.)

So don't use ==. Use if (type.equals(Boolean.class)) {...} instead, and the compiler will be happy and you'll be happy.

答案 1 :(得分:2)

请参阅Object.getClass()的javadoc,value.getClass()的返回类型为Class<? extends |T|>,即Class<? extends Comparable>

表达式value.getClass()的类型在进一步使用之前会经过通配符捕获;因此,我们会在消息中看到捕获的类型Class<capture#2-of ? extends Comparable>

根据JLS #15.21.3,围绕type==Boolean.class的问题取决于是否可以将一种类型转换为另一种类型。如果在编译时可以证明不可能在两种类型之间进行转换是不可能的;在这种情况下,==测试也是禁止的。这是有道理的。

然而,JLS #5.5.1可能有点草率。首先,让我们按照其确切的话。只有当类型参数Class<a1>不能明显区分时,才能将Class<a2>转换为a1, a2。显然,我们不希望Class<Boolean>投放到Class<FLoat>

对于Class<T>Class<Boolean>之间的情况,根据JLS #4.5.1,由于Boolean<:Comparable,它们并不是明显不同的。因此应该允许。你的编译器禁止它,但javac8u45允许它。

遵循相同的推理,在所有情况下都应允许type==Boolean.class。这就是IntelliJ的行为。但javac8没有;它禁止第5和第6案。

重新审核JLS #4.5.1Class<? extends Number>Class<? extends Runnable>将无法比较,因为边界NumberRunnable没有子类型关系。这样会限制太多,因为可能有一个实现Runnable的Number子类。实际上,javac8确实允许对两种类型进行转换或比较。

有趣的是,javac8会禁止比较Class<? extends Boolean>Class<? extends Runnable>。显然,Boolean是最终的事实是一个因素。这听起来很像JLS #5.5.1

对于&#34;可证明不同的&#34;测试时,javac8有时会使用铸造转换测试。

虽然这一切都很糟糕,幸运的是我们总是可以将一个中间演员添加到一个常见的超类型来解决所有问题,所以这不是太大的问题

Dog dog = (Dog)(Animal)cat;

Dog.class == (Class)Cat.class