我遇到了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错误。
答案 0 :(得分:2)
我并不声称能够轻松理解那些复杂的泛型类型,但如果您发现某些代码在javac
中编译而在ecj
中没有(eclipse编译器),那么请提交一个bug报告同时使用Sun和Eclipse并描述情况清晰(如果您还提到您已提交错误报告并提及其各自的网址,则最好),但对于Sun,可能需要一段时间才能公开错误访问)。
我过去做过那件事并得到了很好的回应
由于两个编译器都实现了相同的规范,如果其中只有一个编译代码,其中一个定义错误。
记录:
我尝试使用javac
(javac 1.6.0_13)和ecj
(Eclipse Java编译器0.894_R34x,3.4.2版本)编译示例代码,javac
大声抱怨并失败生成任何.class
个文件,而ecj
仅抱怨一些未使用的变量(警告)并生成所有预期的.class
个文件。
答案 1 :(得分:0)
我最终使用了非泛型:
@Test
public void test() {
ISpace spaceSupertype = new Space();
IAtom atomSupertype = new Atom();
Space space = (Space) spaceSupertype; // ok
Atom atom = (Atom) atomSupertype; // ok
}
答案 2 :(得分:0)
是什么阻碍了你使用没有通配符的类型?
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
}
这样它读得更清楚,加上它编译并运行:) 我认为这将是“解决问题的优雅方式”
答案 3 :(得分:0)
问题可能在于您尝试将IAtom<?, ?>
投射到Atom
(Atom<Atom, Space>
)。系统应该知道?
是否可能是Atom和Space?
如果你不知道要填充泛型的地方的类型,你通常只是放弃整个事情,如
ISpace spaceSupertype = new Space();
生成编译器警告(而不是错误),但您的代码仍将执行(但如果实际类型不兼容,则会出现运行时错误)。
然而,这整件事看起来没有意义。你说你需要强力打字,然后你坚持?
类型去。然后你转过身来试图施展它们。
如果你需要能够将它们转换为Space和Atom,你可能应该只使用它们开始。如果你不能,因为你最终会在这些变量中加入其他类型的代码,那么当你改变运行时类型时,你的代码就会破坏,除非你使用一堆if / then语句(比如在本评论的结尾)。
但是,真的,如果你做的很奇怪,我认为我们正在寻找糟糕的代码设计。重新思考你是如何构建这个的。也许您需要其他类或接口来实现您在此处获得的功能。
问问自己,“我真的需要强大的类型吗?它能让我获得什么?”最好不要添加字段,因为你使用的是接口。 (请记住,如果只通过方法访问字段,那么您只是向公共接口添加方法。)如果它添加了方法,那么使用单个接口IAtom
和ISpace
这里是一个坏主意,因为无论如何你只能使用test()
与该子类型。 test()
不会推广到ISpace
/ IAtom
的其他实现。如果您在此处放置的所有实现都具有test()
所需的相同方法,但并非IAtom
/ ISpace
的所有实现都具有这些方法,则需要一个中间子接口:
public interface IAtom2 extends IAtom
{
[additional methods]
}
然后,您可以在IAtom2
中使用IAtom
代替test()
。然后你自动得到你需要的打字,不需要泛型。请记住,如果一组类都具有公共公共接口(方法和字段集),那么公共接口是超类型或接口的良好候选者。我所描述的是平行四边形,矩形,方形关系,你试图跳过矩形部分。
如果您不打算重新设计,另一个想法是放弃循环泛型,只需通过instanceof
进行实例测试:
if (spaceSupertype instanceof Space)
{
Space space = (Space)spaceSupertype;
...
}
else
...