Java:循环泛型类型关系不允许从超类型转换(javac bug)
题
我遇到了Java编译器的一个完全奇怪的行为。
当循环泛型类型时,我无法将超类型转换为子类型
涉及。
JUnit测试用例重现问题:
public class _SupertypeGenericTest {
interface ISpace<S extends ISpace<S, A>, A extends IAtom<S, A>> {
}
interface IAtom<S extends ISpace<S, A>, A extends IAtom<S, A>> {
}
static class Space
implements ISpace<Space, Atom> {
}
static class Atom
implements IAtom<Space, Atom> {
}
public void test() {
ISpace<?, ?> spaceSupertype = new Space();
IAtom<?, ?> atomSupertype = new Atom();
Space space = (Space) spaceSupertype; // cast error
Atom atom = (Atom) atomSupertype; // cast error
}
}
编译错误输出:
_SupertypeGenericTest.java:33: inconvertible types
found : pinetag.data._SupertypeGenericTest.ISpace<capture#341 of ?,capture#820 of ?>
required: pinetag.data._SupertypeGenericTest.Space
Space space = (Space) spaceSupertype;
^
_SupertypeGenericTest.java:34: inconvertible types
found : pinetag.data._SupertypeGenericTest.IAtom<capture#94 of ?,capture#48 of ?>
required: pinetag.data._SupertypeGenericTest.Atom
Atom atom = (Atom) atomSupertype;
^
2 errors
注意:我正在使用Netbeans最新的trunk,捆绑的Ant,最新的Java 6版本。
我尝试从命令行使用Ant(Netbeans生成build.xml文件)
但它会导致相同的错误。
有什么问题?
是否有一种优雅的方式来解决问题?
奇怪的是:Netbeans没有标记错误(甚至没有警告) 在给定的代码中。
修改强>结果
不,现在我理解没有!
Eclipse 3.4.1既没有标记警告也没有标记错误和编译
代码没有问题!!!
怎么会这样?我想,从命令行使用Ant
Netbeans提供的build.xml将是中立的。
我错过了什么吗?
编辑2:
使用 JDK7 库和JDK7代码格式,netbeans无需编译
错误/警告!点击
(我使用的是1.7.0-ea-b55)
编辑3:
更改标题以表明我们正在处理javac错误。
解决方案
我并不声称能够轻松理解那些复杂的泛型类型,但如果您发现某些代码在javac
中编译而在ecj
(eclipse编译器)中没有,那么请同时提交错误报告< a href =“http://bugs.sun.com/”rel =“nofollow noreferrer”> Sun 和 Eclipse 并描述清楚的情况(如果你还提到你提交了两个错误报告并提到了他们各自的URL,那就最好了,尽管对于Sun来说可能需要一段时间才能公开访问bug。)
我过去做过那件事并得到了很好的回应
- 其中一个团队找出了正确的方法(给出编译错误,警告或什么都没有)
- 并修复了错误的编译器 醇>
由于两个编译器都实现了相同的规范,如果其中只有一个编译代码,其中一个定义错误。
记录:
我尝试使用.class
(javac 1.6.0_13)和<=>(Eclipse Java编译器0.894_R34x,3.4.2版本)编译示例代码并且<=>大声抱怨并且未能生成任何<=>文件,而<=>仅抱怨一些未使用的变量(警告)并生成所有预期的<=>文件。
其他提示
我最终使用了非泛型:
@Test
public void test() {
ISpace spaceSupertype = new Space();
IAtom atomSupertype = new Atom();
Space space = (Space) spaceSupertype; // ok
Atom atom = (Atom) atomSupertype; // ok
}
是什么阻碍了你使用没有通配符的类型?
public void test() {
ISpace<Space, Atom> spaceSupertype = new Space();
IAtom<Space, Atom> atomSupertype = new Atom();
Space space = (Space) spaceSupertype; // no error
Atom atom = (Atom) atomSupertype; // no error
}
这样它读得更清楚,加上它编译并运行:) 我认为这将是<!>“一种优雅的方式解决问题<!>”;
问题可能在于您尝试将IAtom<?, ?>
转换为Atom
(这是Atom<Atom, Space>
)。系统应该知道?
可能是Atom和Space是不是很糟糕?
如果你不知道要填充泛型的地方的类型,你通常只是放弃整个事情,如
ISpace spaceSupertype = new Space();
生成编译器警告(而不是错误),但您的代码仍将执行(但如果实际类型不兼容,则会出现运行时错误)。
然而,这整件事看起来没有意义。你说你需要强力打字,然后你坚持IAtom
类型去哪里。然后你转过身来试图施展它们。
如果你需要能够将它们转换为Space和Atom,你可能应该只使用它们开始。如果你不能,因为你最终会在这些变量中加入其他类型的代码,那么当你改变运行时类型时,你的代码就会破坏,除非你使用一堆if / then语句(比如在本评论的结尾)。
但是,真的,如果你做的很奇怪,我认为我们正在寻找糟糕的代码设计。重新思考你是如何构建这个的。也许您需要其他类或接口来实现您在此处获得的功能。
问问自己,<!>“我真的需要强大的类型吗?它有什么收获?<!>最好不要添加字段,因为你使用的是接口。 (请记住,如果仅通过方法访问字段,那么您只是向公共接口添加方法。)如果它添加方法,那么使用单个接口ISpace
和test()
这是一个坏主意,因为无论如何,你只能使用IAtom2
与该子类型。 instanceof
不会推广到<=> / <=>的其他实现。如果您将在此处放置的所有实现都具有<=>所需的相同方法,但并非所有<=> / <=>的实现都具有它们,则需要一个中间子接口:
public interface IAtom2 extends IAtom
{
[additional methods]
}
然后您可以在<=>中使用<=>代替<=>。然后你自动得到你需要的打字,不需要泛型。请记住,如果一组类都具有公共公共接口(方法和字段集),那么公共接口是超类型或接口的良好候选者。我所描述的是平行四边形,矩形,方形关系,你试图跳过矩形部分。
如果您不打算重新设计,另一个想法就是放弃循环泛型,只需通过<=>进行实例测试:
if (spaceSupertype instanceof Space)
{
Space space = (Space)spaceSupertype;
...
}
else
...