【问题标题】:Java Generics HellJava 泛型地狱
【发布时间】:2011-10-26 15:48:22
【问题描述】:

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

这一切都归结为这段代码。除了对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>)
    }
}

【问题讨论】:

  • 我希望有人能用英语解释一下。我很确定它与Covariance 问题有关,这意味着将ShapeSaver 分配给通用ShapeProcessor 可能会导致运行时异常。但我不确定细节。
  • 是的,我很想听听这个解释。喜欢这个标题。呼应我在这件事上的经验:)
  • +1 让我想起了我不久前问过的这个问题 stackoverflow.com/questions/6065998/… - 这可能是通过使用 .class 丢失信息的类似问题
  • 我喜欢这个问题的标题。有人可以用这个标题写一本书。

标签: java generics nested-generics


【解决方案1】:

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

@Bohemian 的解决方案也应该如此。在推断出T 之后,子类型检查发生在T 的绑定检查中。它也应该失败。这似乎是一个编译器错误,它以某种方式误解了Raw 可分配给Raw&lt;X&gt; 的规则,就好像RawRaw&lt;X&gt; 的子类型一样。另见Enum.valueOf throws a warning for unknown type of class that extends Enum?

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

void genericsHell(Class<? extends ShapeProcessor> a)

确实,ShapeSaverShapeProcessor 的子类型,调用编译。

这不仅仅是一种解决方法。这是有充分理由的。严格来说,对于任何Class&lt;X&gt;X 都必须是原始类型。例如,Class&lt;List&gt; 可以,Class&lt;List&lt;String&gt;&gt; 不行。因为真的没有代表List&lt;string&gt;的类;只有一个类代表List

忽略您不得使用原始类型的严厉警告。考虑到 Java 类型系统的设计方式,我们有时必须使用原始类型。甚至 Java 的核心 API (Object.getClass()) 也使用原始类型。


你可能打算这样做

genericsHell(ShapeSaver<Circle>.class);

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

你可以借一个,例如来自 Guice,您将能够

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

(学习在阅读代码时跳过ShaveSaver&lt;Circle&gt;周围的废话)

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

【讨论】:

  • 太棒了 - 这个答案值得支持,因为它确实建立在波西米亚的 how 之上,并最终得到了 why。 (不幸的是,我只是在使用vox)
  • @irreputable,非常感谢您的详细解释。这个问题,也许并不那么令人惊讶,实际上是我使用 Guice 的结果。我真的希望当我很快开始跳到 Scala 时,我能避免所有这些丑陋的事情。
  • @irreputable,如果您不介意在答案中添加适当的 Guice 函数声明:genericsHell(TypeLiteral extends ShapeProcessor extends Shape>> typeLiteral)
【解决方案2】:

键入genericsHell 方法可以编译:

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

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

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);
}

【讨论】:

  • 那么为什么需要输入方法呢?
  • +1 花了我一段时间,但很有意义,尤其是。阅读链接文章后。因此,键入该方法允许编译器推断原始类型,这会丢失它无法验证的嵌套类型信息。为了说明,在键入方法后,以下调用会导致原始错误:MyClass.&lt;ShapeSaver&lt;?&gt;&gt;genericsHell(ShapeSaver.class)(注意嵌套的&lt;?&gt;
  • 很好的建议,尤其是对于 sscce +1
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-05-29
相关资源
最近更新 更多