简体   繁体   中英

Java: Nested recursive 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.

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>>

The problem with this constructor

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

Is that the superclass constructor is expecting a Class<E> , but the class literal Collection.class is a Class<Collection> . These types are incompatible, because E could be a Collection , a Document , or anything else that might extend Collection .

Any class like Document that extends Collection must supply its own class, so it will be calling the other Collection constructor that takes a Class<E> anyway, so I don't think the Collection() constructor has any use. I would remove it.

Also, in your upper bound for E , you are using the raw form of the very classes you are attempting to make generic. Use

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

and

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

The type Collection is generic, so you must specify the generic type parameter that matches the argument to the Collection constructor.

Collection<Document> c = new Collection<Document>(Document.class);
c = c.getNewInstance();

Document is not itself generic, so this code is still fine:

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

Document must be supplied as a type argument to Collection even when directly creating a Document , because a Document is a Collection<Document> .

Collection<Document> cd = new Document();
cd = cd.getNewInstance();

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM