Java Generics Hell

2022-09-01 16:02:51

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

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

答案 1

的类型为 。当它被馈送到时,编译器需要检查是否是 的子类型,这可以简化为是否是 的子类型。子类型关系不成立,方法调用失败。ShapeSaver.classClass<ShapeSaver>genericsHell()Class<ShapeSaver>Class<? extends ShapeProcessor<?>ShapeSaverShapeProcessor<?>

对于@Bohemian的解决方案来说,情况也应该如此。此处,子类型检查发生在推断 after 的绑定检查处。它也应该失败。这似乎是一个编译器错误,它以某种方式曲解了可分配给的规则,就好像是 的子类型一样。另请参阅 Enum.valueOf 为扩展 Enum 的未知类型的类引发警告?TTRawRaw<X>RawRaw<X>

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

void genericsHell(Class<? extends ShapeProcessor> a)

实际上,是 的子类型,并且调用进行编译。ShapeSaverShapeProcessor

这不仅仅是一种解决方法。这是有充分理由的。严格来说,对于任何 ,都必须是原始类型。例如,是好的,不是。因为实际上没有表示 ;只有一个表示 的类。Class<X>XClass<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()


答案 2

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

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

已编辑:这允许编译器从上下文中或通过编写显式类型来指定,该类型不是字面上的任何类型,而是与作为参数传递的类型相同的类型。如果调用是显式键入的(编译器在后台执行此操作),则代码将如下所示:ShapeProcessorShapeProcessor

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

推荐