为什么编译器在泛型返回类型的方法上抱怨java.lang.Object

时间:2012-04-23 15:36:52

标签: java generics

我正在尝试用Java编写一个小型内部DSL,它会生成一个对象树。 DSL代码如下所示:

RuleBuilder builder = new RuleBuilder(new Syntax());
Syntax s =
builder.rule("rule1")
            .identifier("foo")
            .choice()
                .terminal("bar")
            .end() // 1) Here it works.
       .end() // 2) Here complains the compiler.
       .rule("rule2")
            .identifier("bar")
       .end()
       .build();

编译器抱怨(在2处)java.lang.Object返回的对象end()没有方法rule()。对我来说很明显,Object没有这种方法。构建器代码如下(为简单起见,组装树的代码是:)

class RuleBuilder {
    private final Syntax syntax;

    public RuleBuilder(Syntax syntax) {
        this.syntax = syntax;
    }

    public GenericBuilder<RuleBuilder> rule(String name) {
        return new GenericBuilder<RuleBuilder>(this);
    }

    public Syntax build() { return syntax; }
}

class GenericBuilder<P> {
    private final P parentBuilder;

    public GenericBuilder(P parentBuilder) {
        this.parentBuilder = parentBuilder;
    }

    public <P> P end() {
        return (P)parentBuilder;
    }

    public GenericBuilder<P> identifier(String value) { return this; }
    public GenericBuilder<P> terminal(String value) { return this; }
    public GenericBuilder<GenericBuilder> choice() { return new GenericBuilder<GenericBuilder>(this); }
    // ... other sub node types
}           

我的实现背后的主要思想:生成的语法树由一个语法根节点组成,该节点具有一些规则节点。此规则节点可以具有一些子节点。子节点有两种类型:叶子和节点(为简单起见,在示例代码中仅选择)。我将提供一个流畅的界面,我可以在树中添加节点和叶子。要“结束”树分支,可以使用end()方法返回父构建器。

我尝试解决的问题是,方法end()应该返回父类型构建器对象,该对象可以是RuleBuilderGenericBuilder类型。我不明白为什么它在上面的例子中1)而不是2)工作。

我已经阅读了很多关于泛型的资源,并且我了解List<T>Map<K,V>以及如何工作。我知道“擦除”的事情,类型信息在运行时丢失了。所以我可以理解,在运行时end()返回java.lang.Object时,类型信息被删除。但是我得到了一个编译时错误。我还阅读了Neal Gafter博客关于Super Type Tokens和Typesafe Heterogenous Containers的文章。但我不确定这是否能解决我的问题。我尝试了几种不同的方法(在我在网上搜索时),但现在我被卡住了。

1 个答案:

答案 0 :(得分:4)

有两个问题。首先是这个:

public GenericBuilder<GenericBuilder> choice() { 
    return new GenericBuilder<GenericBuilder>(this);
}

那并不是说GenericBuilder“内在”的是什么。您应该能够将其更改为:

public GenericBuilder<GenericBuilder<P>> choice() { 
    return new GenericBuilder<GenericBuilder<P>>(this);
}

此时,内部end()调用仍然会知道它有GenericBuilder<RuleBuilder>而不是原始GenericBuilder

第二个问题在这里:

public <P> P end() {
    return (P)parentBuilder;
}

当它不应该是一个通用的方法时 - 你不想在这里引入 new 类型参数P;你只想要现有的:

public P end() {
    return parentBuilder;
}