4

I'm working on a fluent API and trying to take advantage of Java's generic methods to offer an elegant API that handles type conversions for my users. I'm running into some trouble getting it working though, due to type erasure.

Here's a simplified version of my interfaces that shows the problem I'm running into:

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() { ... }
}

I'm getting an error on AImpl.appendClause(). The compiler says that A is not within its bound and should extend Query. As far as I can tell, my declaration that AImpl implements A<A> means that A does extend Query<A>.

Following another answer here I tried to break up any potential unresolvable recursion by changing AImpl to:

class AImpl implements A<AImpl> {
  AImpl execute() { ... }
  Query<AImpl> appendClause() { ... }
}

Now I'm getting an error compiling A that says "type parameter T is not within its bound".

Has anybody got any suggestions on how to handle this? Java's generics are giving me a headache.

EDIT

I've changed the definition of A to

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

And that got the second implementation of AImpl working. But I also want to extend the API with the ability to query for subclasses of A:

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

class BImpl implements B<BImpl> {
  BImpl execute() { ... }
  Query<BImpl> appendClause() { ... }
}

This definition gets me an error in B's declaration: "Type parameter B is not within its bound; should extend A".

I can clear up that error by changing B to

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

but now my interface definitions are starting to look ridiculous and I get the feeling I'm doing something wrong. Plus, I'm still getting an error in BImpl: "appendClause() in BImpl cannot implement appendClause() in Query; attempting to use incompatible return type".

Any suggestions on how I can clean up my subclass definitions so I don't need to specify the entire inheritance hierarchy in the extends or how I can get BImpl working?

EDIT 2

Okay, I ran into another issue. I have a factory class that generates Queries:

public class QueryFactory {
  public static <T extends Query<T>> Query<T> queryForType(Class<T> type) { ... }
}

and client code:

Query<B> bQuery = QueryFactory.queryForType(B.class);

My client code is giving me an error in the declaration for bQuery: "Type parameter 'B' is not within its bound; should extend 'Query'". I thought at this point that B did extend Query...

This error goes away if I change the queryForType() call to

Query<? extends B> bQuery = QueryFactory.queryForType(B.class);

but I'm still getting an unchecked warning from the compiler:

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

It looks like type erasure is fighting me again, but I don't understand these warnings. Any more suggestions to get me back on track? Thanks!

EDIT 3

I can get it compiling without warnings if I change the client code to

Query<BImpl> bQuery = QueryFactory.queryForType(BImpl.class);

But I'd really like to hide the implementation classes from users of the API. I tried making B an abstract class instead of an interface in case the problem had to do with that, but it didn't help.

Greg
  • 10,360
  • 6
  • 44
  • 67
  • What should the type Query> represent? I think that signature does not make sense. e.g. I would expect a `Query` to return an instance of `Apple` and not an instance of `Query` as `execute()` does? – Pyranja May 14 '13 at 21:44
  • Apple extends Query. execute() in that case returns an Apple, and you can call appendClause() on the Apple to find a more specific Apple. – Greg May 14 '13 at 21:49
  • Gave you read this write up yet http://www.unquietcode.com/blog/2011/programming/using-generics-to-build-fluent-apis-in-java/ – cmbaxter May 14 '13 at 22:05
  • related: http://stackoverflow.com/questions/7354740/is-there-a-way-to-refer-to-the-current-type-with-a-type-variable – Paul Bellora May 14 '13 at 22:22
  • Thank you for the explanation @Greg . +1 for an interesting question. – Pyranja May 15 '13 at 09:47

1 Answers1

6

Here, your interface A declares type parameter T that extends the raw type A. You should try

//                      v--- Add this
interface A<T extends A<T>> extends Query<T> { }

This makes sure that T extends the genericized A with T. This way, T will be within its bound specified in the Query interface.

This will work with your second version of AImpl that implements A<AImpl>.

EDIT

Having to say

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

looks overly complicated. For the B interface, extend it from A just like you extended A from Query:

//                                    v-- Don't mention B here
interface B<T extends B<T>> extends A<T> { }

Then your BImpl class can look similar to your AImpl class:

class BImpl implements B<BImpl> {
    public BImpl execute() { ... }
    public Query<BImpl> appendClause() { ... }
}
rgettman
  • 176,041
  • 30
  • 275
  • 357
  • Okay, that got rid of the error on A. I'm still running into issues trying to subclass A. Editing my question to reflect the new issues. – Greg May 14 '13 at 21:50
  • I've amended my answer to respond to your amended question. – rgettman May 14 '13 at 22:13
  • I ran into another issue when I tried to add a factory class to generate Queries. I've solved it and am documenting that solution in another answer. – Greg May 15 '13 at 21:44