Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java Generics limitations or wrong usage?

Tags:

java

generics

I have a class representing a pair of two values of the same type (type which can be any of a specific set of types ):

  public class Pair<E extends AClass>{
      private E var1;
      private E var2; 
  }

This class is used by a framework, so it needs a no-argument constructor in which I have to instantiate the 2 variables (var1, var2):

  public class Pair<E extends AClass>{
      private E var1;
      private E var2; 

      public Pair(){
           var1 = invoke constructor of type E; 
           var2 = invoke constructor of type E 
      }
  }

There are obviously a number of problems here:

  1. In order to instantiate the variables I should somehow know its exact type and invoke that specific type's constructor; in the best case this means to have a pretty large if else statement in the constructor, something like:

     public Pair(){
           if(var1 instanceof SpecificType1){
              var1 = new SpecificType1(); 
              var2 = new SpecificType2();
           }
      }
    
  2. Even if I do as above, I will have some problems because var1 is declared of type E and I will get a type mismatch error when trying to instantiate SpecficType1 and to assign the resulted object to var1/var2. In order to make it work, I have to cast to E :

       var1 = (E)new SpecificType1();
    

But this destroys the compile time type checking as I'm trying to cast a specific type to a generic type.

Is this a limitation of the Generics in java or is this scenario a bad one for using Generics ?

like image 651
Razvan Avatar asked Sep 22 '12 23:09

Razvan


3 Answers

In order to instantiate the variables I should somehow know its exact type and invoke that specific type's constructor; in the best case this means to have a pretty large if else statement in the constructor, something like:

You'll run into problems before that.

   if(var1 instanceof SpecificType1){
      var1 = new SpecificType1(); 
      var2 = new SpecificType2();
   }

var1 is null at this point, so var1 instanceof T is false for all T.


One limitation of Java generics is that generic type parameters are erased so there's no way that you can reflect on the type parameter from a zero-argument constructor.

The caller has to provide some context to tell you how to initialize var1 and var2, and the typical way to provide that context is via constructor arguments.


Your best option is probably to let var1 and var2 start off null and then delay initialization until such time as you can get the context you need.

Perhaps

void init(Class<E> type) {
  if (type.isAssignableFrom(ConcreteType1.class)) {
    var1 = type.cast(new ConcreteType1(...));
    var2 = type.cast(new ConcreteType1(...));
  } else { /* other branches */ }
}

This isn't perfect since you still can't distinguish E extends List<String> from E extends List<Number> but it may be good enough for your case, and the .cast method will give you a type-safe cast to E.


Alternatively, Guava, Guice, and related libraries provide things like the Supplier<E> interface which may come in handy in an init method.

like image 64
Mike Samuel Avatar answered Sep 17 '22 18:09

Mike Samuel


You cannot instantiate a generic type - What will happen if for example the generic type is SomeAbstractClass? What will be instantiated? (this is not the reason, it is just intuition)

However, you can use java reflection API to instantiate the object - but you will need the specific class object for it.

A more elegant alternative is using the abstract factory design pattern, and pass a factory object to your pair, and use it to construct the needed object.


Code sample:

public class Pair<S> {
    public final S var1; 
    public final S var2;
    public Pair(Factory<S> builder) {
        var1 = builder.build();
        var2 = builder.build();

    }
}

public interface Factory<S> { 
    public S build();
}

public class IntegerBuilder implements Factory<Integer> {
    private int element = 5;
    public Integer build() {
        return new Integer(element++);
    }
}
like image 33
amit Avatar answered Sep 17 '22 18:09

amit


If a framework were to instantiate it, it would do it as a raw type, something equivalent to new Pair() with no type parameters.

I guess you have to create simple one-liner classes like:

class SpecificType1Pair extends Pair<SpecificType1> {}

and pass them to the framework instead. You can get the actual type parameter as getClass().getGenericSuperclass()).getActualTypeArguments()[0]. You class pair would look like this:

public abstract class Pair<E extends AClass> {
    private E var1;
    private E var2;

    public Pair() {
        ParameterizedType superclass = (ParameterizedType) getClass().getGenericSuperclass();
        @SuppressWarnings("unchecked")
        Class<E> clazz = (Class<E>) superclass.getActualTypeArguments()[0];
        try {
            var1 = clazz.newInstance();
            var2 = clazz.newInstance();
        } catch (InstantiationException e) {
            handle(e);
        } catch (IllegalAccessException e) {
            handle(e);
        }
    }
}
like image 40
Saintali Avatar answered Sep 17 '22 18:09

Saintali