Java: nested recursive generics

I have a set of classes that extend some basic object. The classes in this set can also expand 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 extension classes inherit this.

Here are three example classes defined for my template:

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 setting, I want 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 caused, and I think it also causes compiler errors in the main fetch method. What am I doing wrong and how can I resolve this?

Please note that this is a contrived example related to a bigger problem that I am trying to solve. I understand that this implementation in itself looks stupid.

+5
source share
2 answers

Collection<E...> is a generic type, but your Collection c is a raw type. This means that all of its methods will be processed as raw types, which means that they will return the erase of any common that is.

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

 E getNewInstance() 

erasing

 BaseEntity getNewInstance(); 

This means that c.getNewInstance() returns a BaseEntity , not a Collection , where your compilation error is located.

Document , on the other hand, is not a general class. This means that erasing does not matter at compile time (for these purposes) and that getNewInstance() returns the type E represents, which in this case is equal to Document . So d.getNewInstance() has a return type of Document , and so the string compiles fine.


Aside: whenever you have recursive generics, you should definitely consider the general nature of recursion. For example, in this line:

 BaseEntity<E extends BaseEntity> 

you defined BaseEntity as a generic class, but then immediately ignored its generic text in E extends BaseEntity . This line should be:

 BaseEntity<E extends BaseEntity<E>> 
+7
source

Problem with this constructor

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

Whether the superclass constructor is awaiting Class<E> , but the Collection.class class literal is Class<Collection> . These types are incompatible because E may be a Collection , a Document or anything else that can extend Collection .

Any class, such as Document , that extends Collection , must provide its own class, so it will call another Collection constructor that takes the value of Class<E> , so I don't think the Collection() constructor has any use. I would delete it.

Also, on the top line for E you are using the raw form of the classes themselves that you are trying to generalize. Use

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

and

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

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

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

The document itself is not shared, so this code is still beautiful:

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

Document must be provided as a type argument for Collection even when directly creating a Document , since a Document is a Collection<Document> .

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

Source: https://habr.com/ru/post/1212045/


All Articles