Java泛型类型问题

4

请考虑以下简化示例:

package com.test;

class B<S> {
    B(Class<S> clazz) {}
}

class A<T> {
    class SubB extends B<SubB> {
        SubB() {
            super(SubB.class);
        }
    }
}

尽管IntelliJ没有显示任何错误(通常在存在编译错误时会这样做),但启动程序实际的编译以出现错误,并且错误位于super(SubB.class);

错误:(8,23)java:不兼容的类型:java.lang.Class<com.test.A.SubB>无法转换为java.lang.Class<com.test.A<T>.SubB>

我很好奇,为什么会发生这种情况?我应该如何解决?
采用AdoptOpenJDK 11进行编译。

1
Eclipse报告了编译器错误:“构造函数B<A<T>.SubB>(Class<A.SubB>)未定义” - ernest_k
1
如果删除 <T>,它还能正常工作吗? - assylias
1
@assylias 是的。当 SubB 成为顶级类时也可以工作。我想当 SubB 成为静态嵌套类时也可以工作。 - ernest_k
3
需要将 SubB 设定为 A 的内部类吗?可以改为嵌套类吗?(static class SubB extends B<SubB>)。 - Eran
1
这似乎是因为SubB是一个内部类,所以T也是SubB类型的一部分,但是由于每个T只有一个SubB.class,所以SubB.class无法引用T。尝试将类SubB变为“static class SubB”。或者尝试将SubB.class强制转换为“Class<A<T>.SubB”。 - Erwin Bolwidt
显示剩余4条评论
3个回答

3
这种行为的原因有点复杂。考虑java.util.List.class,它的类型是Class<java.util.List>,而非Class<java.util.List<?>>。这是类字面量的限制。
在你的例子中,SubB.class的类型仍然是Class<com.test.A.SubB>,带有raw type的SubB。但构造函数期望某种类型的Class<com.test.A<T>.SubB>
这就是为什么我们需要将字面值转换为其所需的类型的原因。
super((Class<SubB>) (Class<?>) SubB.class);

这会产生一个警告,但快速检查将表明没有什么可担心的。

1
  1. 根据此链接(https://bugs.openjdk.java.net/browse/JDK-6184881),getClass方法返回原始类型的事实是由规范所指定的,不确定这是否是您所指的限制。
  2. 当您说“...再次使用SubB的原始类型”时,您是指“…A的原始类型”吗?
  3. 虽然该解决方案确实有效,但在我看来它并没有解释为什么会发生这种情况。为什么构造函数期望Class<com.test.A<T>.SubB>?考虑到点(1)规范强制使用具体类型,并且应该是A.class而不是不可能的A<T>.class,那么为什么A<T>甚至出现在编译器消息中呢?
- Eugene
1
当然,一旦你转换为 Class<?> - 你可以进一步转换为任何你想要的类型:Class<Integer> c = (Class<Integer>) (Class<?>) SubB.class; - Eugene
1
请注意,@Eugene,“SubB”不是“static”,因此它具有类型参数(“T”),对应于其外部实例“A<T>”。 “SubB”可以在其主体中引用“T”,例如具有接受或返回“T”的方法。因此,“SubB”是一个泛型类而不是可重构的,因此“SubB.class”引用没有实际类型参数“T”的原始类型“SubB”。我不同意答案的结论,“快速检查将表明没有什么可担心的”。问题的简化示例没有问题,但那肯定不是真正的代码。 - Holger
1
更好的解决方案是使用 class SubB extends B<A.SubB>,以强调传递给 S 的类型参数包含原始类型 A。这样可以消除构造函数中的未检查操作,并且在可能受到 A 影响的 S 的潜在使用处引发警告(如果存在此类地方)。 (如果完全没有意图依赖于 A<T>,则将 SubB 改为 static)。 - Holger
2
@Eugene 给定 Inner 的特定实例,由于类型擦除,您无法确定 useE 的实际参数类型(并且 getClass() 反映了 Inner原始类型)。这些是泛型类的典型属性。我不知道这个名称是否专门用于这样的局部类型,但至少它是一个不可重现的类型,这已经足以产生所有这些问题。 - Holger
显示剩余2条评论

2

我发现这非常有趣。

问题在于当你声明这个变量:

class A<T> {

    class SubB extends B<SubB> {
        SubB() {
            super...
        }
    }
}

这句话的意思是B<SubB>实际上是B<A<T>.SubB>(我甚至不知道这是可能的)。通过使用javap很容易证明这一点(只需反编译类并查看即可)。一旦您将其用“长”形式写出来:

class A<T> {
    class SubB extends B<A<T>.SubB> {
        SubB() {
            super(....);
        }
    }
}

随着深入学习,IT技术开始变得更加易懂。

为了让这个工作正常运行,你必须能够编写:

super(A<T>.SubB.class);

但是Java不允许这样做,.class只能在原始类型上调用。

最好的方法是在此处明确。

class A<T> {
    class SubB extends B<A.SubB> {
        SubB() {
            super(SubB.class);
        }
    }
}

我的意思是:我正在使用类型为A的原始类型:B<A.SubB>


有趣的是,确实如此。有趣的事实是:IntelliJ在super(SubB.class)上显示错误,但启动程序却可以运行。 - Steffen Harbich

1
在泛型中,继承关系不同于我们通常所知道的,例如: 类ArrayList<String>不是List的子类。 而List<String>也不等同于List
此外,在泛型中通常不容易出现编译错误,因为在编译期间泛型类型会被转换为原始类型。
因此,我们需要像@JohannesKuhn提到的那样进行强制转换。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接