Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java: Nested recursive generics

Tags:

java

generics

I have a set of classes that extend some base entity. Classes in this set may also extend from each other creating a nested hierarchy.

My goal is for all classes to have access to a method that creates a new instance of themselves. I want to implement this method in my base entity, so that all extending classes inherit this.

Here are three example classes defined to my pattern:

BaseEntity.java

public abstract class BaseEntity<E extends BaseEntity> {

    Class<E> clazz;

    public BaseEntity(Class<E> clazz) {
        this.clazz = clazz;
    }

    public E getNewInstance() throws IllegalAccessException, InstantiationException {
        return clazz.newInstance();
    }

}

Collection.java

public class Collection<E extends Collection> extends BaseEntity<E> {

    public Collection() {
        super(Collection.class); 
        // compiler error: BaseEntity (java.lang.Class<E>) in BaseEntity cannot be applied to
        //                            (java.lang.Class<Collection>)
    }

    public Collection(Class<E> clazz) {
        super(clazz);
    }

}

Document.java

public class Document extends Collection<Document> {

    public Document() {
        super(Document.class);
    }

}

With this setup, I want to be able to do something like this:

Collection c = new Collection();
c = c.getNewInstance(); // compiler error

Document d = new Document();
d = d.getNewInstance();

Collection cd = new Document();
cd = cd.getNewInstance(); // compiler error

However note that there is a compiler error in the default constructor for Collection.java. I'm not sure why this is being caused, and I think this is also causing the compiler errors in the sample main method. What am I doing incorrectly and how do I resolve this?

Note that this a contrived example pertaining to a bigger problem I'm trying to solve. I understand that this implementation by itself looks silly.

like image 893
user2066880 Avatar asked Jan 27 '15 21:01

user2066880


Video Answer


1 Answers

Collection<E...> is a generic type, but your Collection c is a raw type. That means that all of its methods will be treated as raw types, which means they'll return the erasure of any generic that's there.

Your base class is declared as BaseEntity<E extends BaseEntity>, which means that in this method:

E getNewInstance()

the erasure is

BaseEntity getNewInstance();

That means that c.getNewInstance() returns a BaseEntity, not a Collection, which is where your compilation error comes in.

Document, on the other hand, is not a generic class. That means that the erasure doesn't matter at compile time (for these purposes), and that getNewInstance() returns the type E represents, which in this case is Document. As such, d.getNewInstance() has a return type of Document, and so that line compiles fine.


As an aside: whenever you have recursive generics, you should make sure to account for the generic in the recursion. For instance, in this line:

BaseEntity<E extends BaseEntity>

you've defined BaseEntity as a generic class -- but then immediately ignored its generic in E extends BaseEntity. That line should instead be:

BaseEntity<E extends BaseEntity<E>>
like image 130
yshavit Avatar answered Oct 07 '22 00:10

yshavit