Question

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.

Was it helpful?

Solution

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() { ... }
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top