一尘不染

Java泛型地狱

java

我怀疑以前曾在这里问过(并回答过),但我不知道该如何命名。为什么仅当我不通过类本身时才可以毫无问题地表达通配符?

一切都归结为这段代码。一切正常,除了对的调用genericsHell(ShapeSaver.class)

interface Shape { }

interface Circle extends Shape { }

interface ShapeProcessor<T extends Shape> { }

class CircleDrawer implements ShapeProcessor<Circle> { }

class ShapeSaver<T extends Shape> implements ShapeProcessor<T> { }

class Test {
    void genericsHeaven(ShapeProcessor<? extends Shape> a) {}

    void genericsHell(Class<? extends ShapeProcessor<? extends Shape>> a) {}

    void test() {
        genericsHeaven(new CircleDrawer());
        genericsHeaven(new ShapeSaver<Circle>());
        genericsHell(CircleDrawer.class);
        genericsHell(ShapeSaver.class); // ERROR: The method genericsHell is not applicable for the arguments (Class<ShapeSaver>)
    }
}

阅读 269

收藏
2020-12-03

共1个答案

一尘不染

的类型ShapeSaver.classClass<ShapeSaver>。当将其提供给时genericsHell(),编译器需要检查是否Class<ShapeSaver>为的子类型Class<? extends ShapeProcessor<?>,这将简化为是否ShapeSaver为的子类型ShapeProcessor<?>。子类型关系不成立,方法调用失败。

@Bohemian的解决方案也应如此。在此,子类型检查在推断出T之后的边界检查时发生T。它也应该失败。这似乎是一个编译器错误,它以某种方式曲解了Raw可分配给的规则,就Raw<X>好像Raw是的子类型一样Raw<X>

一个简单的解决方案是声明

void genericsHell(Class<? extends ShapeProcessor> a)

确实ShapeSaver是的子类型ShapeProcessor,并且该调用会编译。

这不只是一种解决方法。有充分的理由。严格来说,对于任何Class<X>,都X必须是原始类型。例如,Class<List>可以,Class<List<String>>不能。因为确实没有代表的阶级List<string>;
只有一个类代表List

忽略将不使用原始类型的严厉警告。给定Java类型系统的设计方式,有时我们必须使用原始类型。甚至Java的核心API(Object.getClass())都使用原始类型。


您可能打算这样做

genericsHell(ShapeSaver<Circle>.class);

不幸的是,这是不允许的。Java可以但没有引入类型文字和泛型。这给很多图书馆带来了很多问题。java.lang.reflect.Type一团糟,无法使用。每个库都必须引入自己的类型系统表示法来解决该问题。

您可以向Guice借钱,例如

genericsHell( new TypeLiteral< ShapeSaver<Circle> >(){} )
                               ------------------

(学习ShaveSaver<Circle>在阅读代码时跳过胡扯)

在的方法主体中genericsHell(),您将拥有完整的类型信息,而不仅仅是类。

2020-12-03