如何为我的流畅API构建接口层次结构?

Gre*_*reg 4 java generics fluent-interface

我正在开发一个流畅的API,并尝试利用Java的通用方法来提供一个优雅的API来处理我的用户的类型转换.由于类型擦除,我遇到了一些麻烦.

这是我的界面的简化版本,显示了我遇到的问题:

interface Query<T extends Query<T>> {
  T execute();
  Query<T> appendClause();
}

interface A<T extends A> extends Query<T> { }

class AImpl implements A<A> {
  A execute() { ... }
  Query<A> appendClause() { ... }
}
Run Code Online (Sandbox Code Playgroud)

我收到了错误AImpl.appendClause().编译器说它A不在其范围内,应该扩展Query.据我所知,我的声明,AImpl实现A<A>该方法A 延长Query<A>.

在这里得到另一个答案后,我尝试通过更改AImpl为:分解任何潜在的无法解析的递归:

class AImpl implements A<AImpl> {
  AImpl execute() { ... }
  Query<AImpl> appendClause() { ... }
}
Run Code Online (Sandbox Code Playgroud)

现在我收到一个错误编译A,说"类型参数T不在其范围内".

有人对如何处理这个问题有任何建议吗?Java的泛型让我很头疼.

编辑

我把A的定义改为了

interface A<T extends A<T>> extends Query<T> { }
Run Code Online (Sandbox Code Playgroud)

这使得AImpl的第二个实现工作.但是我也希望扩展API以查询A的子类:

interface B<T extends B> extends A<B> { }

class BImpl implements B<BImpl> {
  BImpl execute() { ... }
  Query<BImpl> appendClause() { ... }
}
Run Code Online (Sandbox Code Playgroud)

这个定义在B声明中给我一个错误:"类型参数B不在其范围内;应该扩展A".

我可以通过将B更改为来清除该错误

interface B<T extends B<T>> extends A<B<T>> { }
Run Code Online (Sandbox Code Playgroud)

但现在我的界面定义开始看起来很荒谬,我觉得我做错了.另外,我在BImpl中仍然遇到错误:"BImpl中的appendClause()无法在Query中实现appendClause();尝试使用不兼容的返回类型".

关于如何清理我的子类定义的任何建议,所以我不需要指定整个继承层次结构extends或如何让BImpl工作?

编辑2

好的,我遇到了另一个问题.我有一个生成查询的工厂类:

public class QueryFactory {
  public static <T extends Query<T>> Query<T> queryForType(Class<T> type) { ... }
}
Run Code Online (Sandbox Code Playgroud)

和客户代码:

Query<B> bQuery = QueryFactory.queryForType(B.class);
Run Code Online (Sandbox Code Playgroud)

我的客户端代码在bQuery的声明中给出了一个错误:"类型参数'B'不在其范围内;应该扩展'Query'".我认为在这一点上B确实扩展了Query ...

如果我将queryForType()调用更改为,则此错误消失

Query<? extends B> bQuery = QueryFactory.queryForType(B.class);
Run Code Online (Sandbox Code Playgroud)

但我仍然从编译器得到一个未经检查的警告:

unchecked method invocation: <T>queryForType(Class<T>) in QueryFactory is applied to Class<B>
unchecked conversion found: Query required: Query<B>
Run Code Online (Sandbox Code Playgroud)

看起来类型擦除再次打击我,但我不明白这些警告.还有什么建议可以让我回到正轨吗?谢谢!

编辑3

如果我将客户端代码更改为,我可以在没有警告的情况下进行编译

Query<BImpl> bQuery = QueryFactory.queryForType(BImpl.class);
Run Code Online (Sandbox Code Playgroud)

但我真的想隐藏API用户的实现类.我试图使B成为抽象类而不是接口,以防问题与此有关,但它没有帮助.

rge*_*man 6

这里,您的接口A声明T了扩展原始类型的类型参数A.你应该试试

//                      v--- Add this
interface A<T extends A<T>> extends Query<T> { }
Run Code Online (Sandbox Code Playgroud)

这确保了T扩展泛化AT.这样,T将在其Query界面中指定的界限内.

这将适用于您的第二个版本的AImpl实现A<AImpl>.

编辑

不得不说

interface B<T extends B<T>> extends A<B<T>> { }
Run Code Online (Sandbox Code Playgroud)

看起来过于复杂.对于B接口,从扩展它A就像你扩展AQuery:

//                                    v-- Don't mention B here
interface B<T extends B<T>> extends A<T> { }
Run Code Online (Sandbox Code Playgroud)

然后你的BImpl班级看起来类似于你的AImpl班级:

class BImpl implements B<BImpl> {
    public BImpl execute() { ... }
    public Query<BImpl> appendClause() { ... }
}
Run Code Online (Sandbox Code Playgroud)