Java: Nested recursive generics - java

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

Related

Generic method return type - compilation error [duplicate]

This question already has an answer here:
Java generics : Type mismatch: cannot convert from Integer to K
(1 answer)
Closed 5 years ago.
Given this code sample:
class A {
}
public class TestGenerics {
private static <T extends A> T failsToCompile() {
return new A();
}
private static <T extends A> T compiles(T param) {
return param;
}
}
how come the first method doesn't compile, but the second one does?
The error is:
Incompatible types. Required: T. Found: A
Essentially what I'm trying to achieve is to return a concrete instance of a sub-type (for example of class B, that extends A). Sort of a factory method.
Edit: OK, for those who downvoted, I decided to elaborate a bit further. See the updated example, which no longer uses String (yes I'm aware that String is final, no need to state the obvious)
Edit 2: alternative version of the question:
How would you implement this method so that it compiles (without unchecked casts and warnings)?
abstract <T extends A> T failsToCompile();
Edit 3: Here's a code sample closer to the problem I'm trying to solve:
class Foo { }
class Bar extends Foo { }
class A<T extends Foo> { }
class B extends A<Bar> { }
public class TestGenerics {
private static <T extends Foo> A<T> failsToCompile() {
return new B();
}
}
Here, the method return type is A<T>. Considering that T is defined as a Foo or its subclass, and the definition of B is as follows: B extends A<Bar>, why can't I just return new B()? I guess the problem comes down to the fact that you can't assign List<Animal> dList = new ArrayList<Dog>(); and I understand why. But is there an elegant solution to this?
Ignoring the fact that String is final (so you can't have a sub-class of String), you can pass the generic class Class<T> into the method for the generic case. Like,
private static <T extends String> T get(Class<T> cls)
throws InstantiationException, IllegalAccessException {
return cls.newInstance();
}
I'm trying to elaborate my comment a bit more into an answer to the "why".
What you're showing in your question are generic methods (and not generic types), see e.g. Oracles "The Java™ Tutorials", Generic Methods.
Reasoning
Let's consider the explicit call for the working method: TestGenerics.<A>compiles(new A());
This instantiates the generic type T of your method call to class A. Remember that type erasure removes all these funky generic types when compiling, that the JVM doesn't see generic types and that the compiler handles generics. The compiler knows now, that you want to call the method with the generic type T "set" to class A. From that, it can deduct that the returned type is also of class A (as T is returned according to the methods code and as T is instantiated to A). That's all you gain here from using generics.
You can remove the <A> from your method call and make it look like an "ordinary" method call, as the compiler can deduct that T has to be A: TestGenerics.compiles(new A());
Let's look at TestGenerics.<B>compiles(new B()); with class B extends A {}. As above, the compiler knows that this method call will return an object of class B.
Now imagine the (not compiling) code TestGenerics.<B>compiles(new A());. The compiler will throw an error as the object passed to the method is not of type B. Otherwise the method would return an object of class A - but the generic type asserts that the method returns here an object of typeB.
That's actually equivalent to the example (B b = failsToCompile()) Andreas gave in his comment.
Until you instantiate a generic type - even if you set bounds (like T extends A) - it doesn't have a class type. You therefore can't return a concrete class, as this would not satisfy the generic type parameter.
Pizza Example
Just for fun, let's try to make a real world example for above reasoning: Pizza. For the sake of the example, let's assume that every Pizza is a subtype of the Margherita, i.e. you add ingredients to a Margherita to get your favorite other pizza.
class PizzaMargherita {};
class PizzaProsciutto {
PizzaProsciutto() {
super();
addIngredient(new Prosciutto());
}
}
Your compiles() method now bakes the pizza:
public static <P extends Margherita> P bake(P pizza) {
heat(pizza);
return pizza;
}
Bake a Margherita and get a (baked) Margherita out of your oven, bake a Prosciutto and get a Prosciutto.
Now think of this:
public static <P extends Margherita> P doesntBake(P pizza) {
return new PizzaMargherita();
}
An oven always returning a Margherita, independent of what you put into it ?!? The compiler doesn't approve that.
-> You need the concrete pizza to bake it or you need a workaround, like the type token:
public static <P extends Margherita> P bakePizza(Ingredients<P> ingredient) {
P pizza = buildPizza(ingredients);
return bake(pizza);
}
Workaround type token
You have to use a runtime-type token as #Elliott Frisch shows in his answer:
private static <T extends A> T failedToCompile(Class<T> c)
throws InstantiationException, IllegalAccessException {
return c.newInstance();
}
You can't instantiate a generic type - new T() doesn't work, as the JVM doesn't know anything about the generic type T. What you're looking for in edit 2 therefore doesn't work. But TestGenerics.<A>failedToCompile(A.class); works, as A.class is part of the java byte code.
Workaround type token & generic class
Depending on your specific requirements, a generic factory class might help you:
class Factory<T extends A> {
private final Class<T> typeToken;
public Factory(Class<T> typeToken) {
this.typeToken = typeToken;
}
public T build()
throws InstantiationException, IllegalAccessException {
return this.typeToken.newInstance();
}
}
You will still need some form of Map to get the correct factory to build the class you need, but you can now use whatever is available at the point where you need to create the Object of type T.
Map<String, Factory<?>> factories = new HashMap<>();
Map<DAO, Factory<?>> factories = new HashMap<>();
Map<ValueObject, Factory<?>> factories = new HashMap<>();
factories.get(valueObjectTypeA);
Others have clearly explained why the failsToCompile method doesn't compile. Andreas has even shown an example...
As to how to circumvect this, I think you should consider using a Supplier, which is just the factory you need:
private static <T extends A> T nowItCompilesFine(Supplier<? extends T> factory) {
return factory.get();
}
And you call it:
A a = nowItCompilesFine(() -> new A());
Or using a reference to a constructor:
A a = nowItCompilesFine(A::new);
Same with any descendant of A:
class Child extends A { }
List<Supplier<? extends A>> factories = Arrays.asList(A::new, Child::new);
Supplier<? extends A> factoryA = factories.get(0);
A a1 = nowItCompilesFine(factoryA);
Supplier<? extends A> factoryChild = factories.get(1);
A a2 = nowItCompilesFine(factoryChild);
System.out.println(a2.getClass().getSimpleName()); // Child

Can generics infer a related type?

Given
public class Foo {
public static class FooBuilder { ... }
}
I want to write a method on a third class that returns Foo, given Foo.FooBuilder.class
i.e.
Foo f = x.make(Foo.FooBuilder.class, someData);
Is it possible to declare a signature using generics that can imply the return type? Is there some language feature that lets me say "type U is outer class of type T"?
Obviously, it is possible to specify that type extends, or is the base of, a generic type (U extends T or U super T, respectively) but I am looking for U outer T which is, I think, more than Java can offer, even indirectly, at least in 1.7, which I am targeting.
So far, I have simply declared both inner and outer types, which works but is a wider definition than I am after and looks clumsy too.
public <TYPE,BUILDER> TYPE make(Class<BUILDER> builderClass, Map<String,Object> data) {
// Construct TYPE
}
Is there a way to infer TYPE without explicitly providing a template parameter?
There is a Class#getDeclaringClass method that may work in your case.
Quoting the docs:
If the class or interface represented by this Class object is a member of another class, returns the Class object representing the class in which it was declared.
EDIT:
After the clarification of OP, here is the new suggestion:
You create an generic interface to mark all your nested classes:
public interface Nested<P> {
}
Then you apply it to your Foo.Bar class like this:
public class Foo {
public static class Bar implements Nested<Foo> {
}
}
Then in your factory you can have the following:
public <P> P make(Class<? extends Nested<P>> clazz, Map<String, Object> someData) {
// do whatever you need to do
return (P) clazz.getDeclaringClass();
}
However, with this construct, there is not way to validate it your nested class is the real class, declared when implementing the generic interface.

Java nested genericsType

I got an interesting issue. Consider the following code:
public class GenericsTest
{
// An interface with a generic type.
public interface IObject<K>{}
// An class with a generic type
public static class ObjectA<K>
{
// An inner class without generic type, but implementing the interface with generic Type
// When adding a genericType to this class, it will popup the warning: 'hiding'
public class ObjectB implements IObject<K>
{
}
// A getter with the interface as return Type
public IObject<K> getObjectB()
{
return new ObjectB();
}
}
public ObjectA<String> objectA = new ObjectA<String>();
// This field is yelling for an genericType, though it can't get one because the class doesn't support a generic argument.
public ObjectB genericObject = (ObjectB)objectA.getObjectB();
}
So the issue is that my IDE is complaining about a missing genericType of the genericObject field, and that I should add a SupressWarning annotation to the method. (luckily not code breaking, though still pretty annoying).
I could add a generic type to the inner class, though than it would 'hide' a generic argument, meaning I would need to add a SupressWarning annotation there.
A second fix would be to use a second generic type like <S extends K>. In which case I don't need a SupressWarning annotation at the class. Though when I try to use the getter, my IDE is complaining:
The member type GenericsTest.ObjectA.ObjectB<String> must be qualified with a parameterized type, since it is not static.
So basically I can't use the getter, unless I add an argument of the genericType to the method.
My question is, what is the cleanest way to solve this problem without changing the inner class to a nested class?
Here's a short example that compiles with no issues:
public class Test
{
interface K<T> { }
static class A<T>
{
class B implements K<T> { }
public K<T> getK() { return new B(); }
}
A<String> a = new A<String>();
A<String>.B b = (A<String>.B) a.getK();
}
Notice the last line:
A<String>.B b = (A<String>.B) a.getK();
To be honest, I'm not sure how the example you've given even compiles as far as it does - the class 'ObjectB' is not visible from the main 'GenericsTest' scope, it needs to be prefixed with its' parent class.

How to build a generic entity repository in Java

I have a generic repository class that looks like this:
public class HibernateRepository<E extends Object, EId>
implements EntityRepository<E, EId> {
// Many things that are working fine
#Override
public E getById(EId id) {
return entityManager.getReference(E.class, id); // <<< Error here!
}
}
The method getById() is supposed to return a certain entity instance given its Id. Something very trivial in many other situations, but not in this one, since the compiler returns:
Illegal class literal for the type parameter E
Notice that I don't want to pass the desired class to the method, since I already did that in the class definition.
The usage for this class could be:
public class MyClassRepositoryHibernate
extends HibernateRepository<MyClass, Long> {
}
Now this new repository works on MyClass instances and the Id is typed with Long.
Is that possible in Java?
You will have to pass the actual class type to the constructor of your HibernateRepository and use that throughout your methods. The JVM has no knowledge of what "E" is at runtime hence you need to provide the concrete class type.
You can ensure you don't instantiate the object incorrectly by using the Generic parameter type in the constructor, like this:
public class HibernateRepository<E extends Object, EId>
implements EntityRepository<E, EId> {
private Class<E> clazz;
public HibernateRepository(Class<E> clazz) {
this.clazz = clazz;
}
#Override
public E getById(EId id) {
return entityManager.getReference(clazz, id); // <<< Error here!
}
}
A pain, but a necessity due to type erasure.
The type will be erased after compilation and thus is not available. This is called Type Erasure. You have to pass the Class object somewhere.

AbstractFactory with generic types in Java: a design problem

I have the following 2 interfaces accordingly to abstract factory pattern:
public interface GenericObjectInterface<T extends Number>{
public T getResult();
}
public interface AbstractFactoryInterface{
public <T extends Number> GenericObjectInterface<T> createGenericObject();
}
I have an abstract class implementing GenericObject, but it's still unaware of the concrete type (it does only generic operations on Number):
public abstract class GenericAbstractClass<T extends Number> implements GenericObjectInterface<T>{ }
Then I have a series of concrete class extending that perform generic parameter substitution:
public class IntegerObject extends GenericAbstractClass<Integer>{
public Integer getResult(){}
}
....
Now, from inside an implementation of the factory I build the concrete type, that's implementing GenericObjectInterface but has lost it's generic parameter:
public class ConcreteFactory{
public <T extends Number> GenericObjectInterface<T> greateGenericObject(Class<T> c){
if (c.class.isInstance(Integer.class)){
IntegerObject obj = new IntegerObject();
//I would like to return obj
GenericObjectInterface<T> a = new IntegerObject(); //errror
GenericAbstractClass<T> a = new IntegerObject(); //errror
return a;
}else if (c.class.isInstance(Double.class)){
}
}
}
I would like to return obj that implements GenericObjectInterface but I don't know how can I do it.
how can I solve this?
I'm used to abstract factory but I've never used it with generics. Am I doing some mistakes in interpreting the pattern?
If your method returns an IntegerObject why don't you just return GenericObjectInterface<Integer>? You already know the parameter type.
In that case, just add a generic parameter to AbstractFactoryInterface, too:
public interface AbstractFactoryInterface<T extends Number> { ... }
public class ConcreteFactory implements AbstractFactoryInterface<Integer> { ... }
In your implementation the type of T would be inferred from the assignment, and thus you could do this:
GenericObjectInterface<Double> g = new ConcreteFactory().greateGenericObject();
In that case T would be Double but you'd use Integer internally, resulting in this:
GenericObjectInterface<Double> a = new IntegerCell();
Since the compiler can't ensure that T will always be of type Integer it won't allow you to do that assignment.
Abstract factory is characterized by the factory method returning an interface or abstract class reference instead of the concrete reference. It does not extend to type parameters.
Think of it this way: should you be able to do this?
public class ConcreteListFactory {
public <T> List<T> createList() {
return new ArrayList<String>();
}
}
What if the caller wanted a List<Integer>?
If you want your factory to return a generified type, you should have your concrete class accept the type parameter. Otherwise have your factory method return a GenericObjectInterface<Integer>.
Alternatively, you could have your method accept a type token (Integer.class). For example:
public <T extends Number> GenericObjectInterface<T> createGenericObject(Class<T> clazz) {
if ( clazz.equals(Integer.class) ) {
return (GenericObjectInterface<T>) new IntegerObject();
}
}
This will result in an unchecked cast warning but you can prove to yourself that it is safe, and thus suppress the warning or ignore it.
Generally, factories are not implemented as generics because you can't examine the type of the generic to determine the type of object to create (you can't do T.getClass) which is why #Mark's example causes the class to be passed in as an argument.
I think, more usually you would have multiple concrete factories. One for each Number type that you intend to support.
public interface AbstractFactoryInterface<T extends Number> {
public GenericObjectInterface<T> createGenericObject();
}
class IntegerFactory implements AbstractFactoryInterface<Integer>...
class LongFactory implements AbstractFactoryInterface<Long>...
You could then create a Map<Class, AbstractFactoryInterface>...
Map<Class, AbstractFactoryInterface> myMap = ...;
myMap.put(Integer.class, new IntegerFactory());
myMap.put(Long.class, new LongFactory ());
casting is perfectly fine here. if c==Integer.class, then T=Integer, casting GOI<Object> to GOI<T> is absolutely correct. It is a checked cast because you have checked that T=Integer before casting, therefore the unchecked warning can be legitimately suppressed.

Categories