Java Generics Hell

时间:2011-08-14 03:38:17

标签: java generics nested-generics

我怀疑此处已被问及(并已回答),但我不知道如何命名问题。为什么只有在我没有通过课程本身时才能毫无问题地表达通配符?

这一切归结为此代码。除了拨打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>)
    }
}

2 个答案:

答案 0 :(得分:17)

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

同样的事情应该适用于@ Bohemian的解决方案。这里子类型检查发生在推断T之后T的绑定检查时。它也应该失败。这似乎是一个编译器错误,它以某种方式错误解释了Raw可分配给Raw<X>的规则,就像RawRaw<X>的子类型一样。另见Enum.valueOf throws a warning for unknown type of class that extends Enum?

解决问题的一个简单方法是声明

void genericsHell(Class<? extends ShapeProcessor> a)

的确,ShapeSaverShapeProcessor的子类型,并且调用会编译。

这不仅仅是一种解决方法。这是有充分理由的。严格来说,对于任何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()的方法正文中,您将获得完整的类型信息,而不仅仅是类。

答案 1 :(得分:11)

键入genericsHell方法可以编译:

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

EDITED:这允许编译器从上下文或通过编码显式类型指定ShapeProcessor不是字面任何 ShapeProcessor,而是与作为参数传递的类型相同的类型。如果调用是显式类型的,(编译器在封面下执行),代码将如下所示:

MyClass.<ShapeSaver>genericsHell(ShapeSaver.class);

有趣的是,它提供了一个类型警告,但仍然可以编译。但是,不需要显式类型,因为参数中有足够的类型信息可用于infer泛型类型。


你的问题遗漏了一些声明,所以我添加了它们来创建一个Short Self-Contained Correct Example - 即这段代码按原样编译

static interface Shape { }

static interface Circle extends Shape { }

static interface ShapeProcessor<T extends Shape> { }

static class CircleDrawer implements ShapeProcessor<Circle> { }

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

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

// The change was made to this method signature:
static <T extends ShapeProcessor<?>> void genericsHell(Class<T> a) { }

static void test() {
    genericsHeaven(new CircleDrawer());
    genericsHeaven(new ShapeSaver<Circle>());
    genericsHell(CircleDrawer.class);
    genericsHell(ShapeSaver.class);
}