我正在尝试用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()
应该返回父类型构建器对象,该对象可以是RuleBuilder
或GenericBuilder
类型。我不明白为什么它在上面的例子中1)而不是2)工作。
我已经阅读了很多关于泛型的资源,并且我了解List<T>
和Map<K,V>
以及如何工作。我知道“擦除”的事情,类型信息在运行时丢失了。所以我可以理解,在运行时end()
返回java.lang.Object
时,类型信息被删除。但是我得到了一个编译时错误。我还阅读了Neal Gafter博客关于Super Type Tokens和Typesafe Heterogenous Containers的文章。但我不确定这是否能解决我的问题。我尝试了几种不同的方法(在我在网上搜索时),但现在我被卡住了。
答案 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;
}