7

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.

1
  • 3
    Conflicting names with a couple of popular java library classes causes confusion... Commented Jan 27, 2015 at 21:17

2 Answers 2

8

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>>
Sign up to request clarification or add additional context in comments.

4 Comments

Makes total sense, great explanation. I think part of my problem is that I'm having trouble wrapping my head around recursive generics. BaseEntity<E extends BaseEntity<E>> is an example I have a hard time grasping. But I'll just have to spend more time understanding, thanks again.
Could you explain the difference between BaseEntity<E extends BaseEntity<E>> and BaseEntity<E extends BaseEntity<?>> ? They both seem valid ways to resolve recursive generics, but I've noticed differences in the way the compiler handles them.
That's a bit bigger than a comment -- it may be worth a followup question (I didn't find anything with "java recursive generics with wildcard"). But the difference is basically when your generic class itself uses methods on E. Consider if it had a method that took E entity and called entity.getNewInstance(). With extends BaseEntity<E>, you'd get back an E. With extends BaseEntity<?>, you'd get back a BaseEntity<?>.
there's no point here to do BaseEntity<E extends BaseEntity<E>> over just BaseEntity<E>
3

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();

2 Comments

Super helpful. I understand the discrepancies in the main method. One problem I'm still having is even after changing the class headers, I still have the same compiler error in my Collection default constructor
I think that the default constructor for Collection is useless. I have recommended to remove it. What good is a Collection<Collection>, when you must be after a concrete subclass? Besides, that would force you to write Collection<Collection<Collection>>, which would force Collection<Collection<Collection<Collection>>>, and more levels of generics ad infinitum.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.