27

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

这一切都归结为这段代码。一切都按预期工作,除了调用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>)
    }
}
4

2 回答 2

18

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

@Bohemian 的解决方案也应该如此。在这里,子类型检查发生在推断T之后的绑定检查时。T它也应该失败。这似乎是一个编译器错误,它以某种方式误解了Raw可分配给的规则,Raw<X>好像RawRaw<X>. 另请参阅Enum.valueOf 对扩展 Enum 的未知类型的类抛出警告?

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

void genericsHell(Class<? extends ShapeProcessor> a)

实际上,ShapeSaver是 的子类型ShapeProcessor,并且调用编译。

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

于 2011-08-14T21:37:02.020 回答
11

键入该genericsHell方法允许它编译:

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

已编辑:这允许编译器从上下文中指定,或通过编码显式类型,这ShapeProcessor不是字面上的any ShapeProcessor,而是与作为参数传递的类型相同的类型。如果调用是显式类型的(编译器在幕后做的),代码将如下所示:

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

有趣的是,它给出了一个类型警告,但仍然可以编译。然而,显式类型不是必需的,因为可以从参数中获得足够的类型信息来推断泛型类型。


您的问题缺少一些声明,因此我将它们添加到创建一个简短的自包含正确示例- 即此代码按原样编译

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);
}
于 2011-08-14T04:14:45.000 回答