Java:循环泛型类型关系不允许从超类型转换(javac bug)

时间:2009-04-29 20:59:05

标签: java generics casting types

我遇到了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错误。

4 个答案:

答案 0 :(得分:2)

我并不声称能够轻松理解那些复杂的泛型类型,但如果您发现某些代码在javac中编译而在ecj中没有(eclipse编译器),那么请提交一个bug报告同时使用SunEclipse并描述情况清晰(如果您还提到您已提交错误报告并提及其各自的网址,则最好),但对于Sun,可能需要一段时间才能公开错误访问)。

我过去做过那件事并得到了很好的回应

  1. 其中一个团队弄清楚了正确的方法是什么(给出编译错误,警告或什么都没有)
  2. 并修复了错误的编译器
  3. 由于两个编译器都实现了相同的规范,如果其中只有一个编译代码,其中一个定义错误。

    记录:

    我尝试使用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<?, ?>投射到AtomAtom<Atom, Space>)。系统应该知道?是否可能是Atom和Space?

如果你不知道要填充泛型的地方的类型,你通常只是放弃整个事情,如

ISpace spaceSupertype = new Space();

生成编译器警告(而不是错误),但您的代码仍将执行(但如果实际类型不兼容,则会出现运行时错误)。

然而,这整件事看起来没有意义。你说你需要强力打字,然后你坚持?类型去。然后你转过身来试图施展它们。

如果你需要能够将它们转换为Space和Atom,你可能应该只使用它们开始。如果你不能,因为你最终会在这些变量中加入其他类型的代码,那么当你改变运行时类型时,你的代码就会破坏,除非你使用一堆if / then语句(比如在本评论的结尾)。

但是,真的,如果你做的很奇怪,我认为我们正在寻找糟糕的代码设计。重新思考你是如何构建这个的。也许您需要其他类或接口来实现您在此处获得的功能。

问问自己,“我真的需要强大的类型吗?它能让我获得什么?”最好不要添加字段,因为你使用的是接口。 (请记住,如果只通过方法访问字段,那么您只是向公共接口添加方法。)如果它添加了方法,那么使用单个接口IAtomISpace这里是一个坏主意,因为无论如何你只能使用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
...