Showing posts with label Generics. Show all posts
Showing posts with label Generics. Show all posts

Tuesday, 31 July 2012

Java Generics IV - Incompatible Types, Reifed

How to Solve: "Uncompilable source code - incompatible types"?


The following piece of code:
public class MyClass {

  public static class A {};

  protected HashSet<A> theSet = new HashSet<A>();

  public Set<Object> getSet() {
    return (Set<? extends Object>) theSet;
  }

  public static void main(String[] args) {
    MyClass mc = new MyClass();
    Set<Object> s = mc.getSet();
  }

}
generates the following error at compile time:

  test3/MyClass.java:[13,15] incompatible types
  found   : java.util.Set<capture#128 of ? extends java.lang.Object>
  required: java.util.Set<java.lang.Object>

The HashSet<A> cannot be cast into a Set<Object>. One needs to create an new Set<Object> and copy the content of HashSet<A> to it:
public Set<Object> getSet() {

  Set<Object> result = new HashSet<Object>();
  result.addAll(theSet);
  
  return result;

  // return (Set<? extends Object>) theSet;

}

What Does Reified Mean in the Context of Java Generics?

It means that if you define a Set<String> and a Set<Integer>, Java does not (directly) provide the mean to retrieve the type used to instantiate the generics. In other words, it is not (easily) possible to retrieve String and Integer when inspecting the sets at runtime.


Part I  Part II   Part III   Part IV

Java Generics III - Array Initialization, Static Methods, Extend Class & Interfaces

How to Initialize an Array of Generic Type?

The following code will not compile:
public class A<V> {

  private Set<V>[] sets;

  public A(int size) {
     sets = new HashSet<V>[size];
  }

}
The solution is to use a cast:
     sets = (Set<V>[]) new HashSet[size];

How to Make a Static Method Generic?

The following code indicates how to proceed:
public class B<T> {

    // Allowed
    public static <U> U myStaticMethod(U u) {
        return ...;
    }

    // Not allowed
    public static T myStaticMethod2(T t) {
        return ...;
    }

}

How to Make a Generic Extends a Class and Multiple Interfaces?

This is a generic wildcard issue. Considering a class C, and two interfaces I1 and I2:
public class D<T extends C & I1 & I2> {
    
    // ...

}

The class must precede the interfaces. A generic can only extends one class at most.

Part I  Part II   Part III   Part IV

Monday, 30 July 2012

Java Generics II - Retrieving Type at Runtime

How to Retrieve the Type of a Generic at Runtime?

This is a very common question repeated over and over in forums and blogs. The answer is simple: it is impossible unless you use a workaround (or some kind reflexion, when possible, as we will see next).

In other words, there is no way you can achieve something like this:
public class MyQueue<T> extends PriorityQueue<T> {

    public boolean canTakeSuchElement(Object o) {
        return T.getClass().isInstance(o);
    }

}
Many have tried, nearly all failed (*). One workaround is the following:
public class MyQueue<T> extends PriorityQueue<T> {

    private Class<T> myElementType;

    public MyQueue(Class<T> elementType) {
        myElementType = elementType;
    }

    public boolean canTakeSuchElement(Object o) {
        return myElementType.isInstance(o);
    }

}

Which you can use as following:
MyQueue<String> mq = new MyQueue<String>(String.class);

if ( mq.canTakeSuchElement(333) ) {
    // No way...
} else if ( mq.canTakeSuchElement("azjsjdl") ) {
    // Ok...
}

Does Java Keep Any Trace of the Generic Instantiation Type at Runtime?

Contrary to the information commonly spread over the Internet, Java does keep some information at runtime about the types used to 'instantiate' generics. But it is not easy to retrieve it. See (*) and the next question.

How to Retrieve the Generic Type of a List, Set, Map... at Runtime?

It is possible using reflexion:
public class ExtractGenerics {

    public static class MyClass {

        List<String> l = new ArrayList<String>();
        Set<Integer> s = new HashSet<Integer>();
        Map<Double,String> m = new HashMap<Double,String>();

    }

    public static Class<?>[] extractGenericTypes(Field f) {

        ParameterizedType stringListType =
            (ParameterizedType) f.getGenericType();

        Type[] ata = stringListType.getActualTypeArguments();

        Class<?>[] result = new Class[ata.length];

        for (int i=0;i<ata.length;i++) {
            result[i] = (Class<?>) ata[i];
        }

        return result;

    }

    public static void printGenericTypes(Class<?>[] cs) {

        System.out.println("------");
        for ( Class c : cs ) {
            System.out.println(c);
        }
    }

    public static void main(String... args) throws Exception {

        printGenericTypes(
          extractGenericTypes(
            MyClass.class.getDeclaredField("l")));

        printGenericTypes(
          extractGenericTypes(
            MyClass.class.getDeclaredField("s")));

        printGenericTypes(
          extractGenericTypes(
            MyClass.class.getDeclaredField("m")));

    }

}

The above produces:

  ------
  class java.lang.String
  ------
  class java.lang.Integer
  ------
  class java.lang.Double
  class java.lang.String


(*) The concept of super token type has been introduced by Neil Gafter. It can be exploited via some complicated tricks to extract the Type (not a Class instance) used to 'instantiate' the generic type. Ian Robertson provides technical explanation here.

Although we won't develop it here, super token types can prove useful to create instances of the type used to 'instantiate' the generic type, when possible. If, with MyClass<T>, you create MyClass<List<String>>, you cannot instantiate a List<String>. However, if you create MyClass<ArrayList<String>>, you can instantiate ArrayList<String> (more on this in Neil Gafter's post mentioned earlier).

Yet, this solution does not cover well for constructors with parameters.


Part I  Part II   Part III   Part IV

Java Generics I - Erasure, Benefits

Using Java Generics can be tricky. New developers often have a hard time grasping the nuts and bolts of this feature. These posts summarize the most common pitfalls and caveats.

How Are Generics Compiled in Java?

Technically speaking, generics are not compiled into bytecode in Java. They are preprocessed by the compiler, before the Java code itself is compiled into bytecode. This process is called type erasure.

This is best described with an example:
Queue<String> q = new PriorityQueue<String>();
q.add("eeee");
String retr = q.element();

The above code in transformed into the following equivalent bytecode:
Queue q = new PriorityQueue();
q.add("eeee");
String retr = (String) q.element();

The value returned by q.element() is cast into a String.

What is the Benefit of Generics?

The main benefit is to check at compile time that all elements inserted or retrieved from the q object are strings, because after compilation, any element could technically be inserted in it. This could trigger unexpected errors, such as ClassCastException at runtime.

If some external code imported the q object defined in the above generic Java code, the compiler would check that this external code uses q properly. In others words, it would check that it only inserts or retrieves strings from it.

Like this, one is sure that bytecode won't throw ClassCastException at runtime. It also means that the generic code does not need to be recompiled, only the external code needs compilation.

By not introducing generics at the bytecode level, Java code written and compiled before generics were available can still use Java code using generics. The future is safe for code of the past.


Part I  Part II   Part III   Part IV