11

I have below generic method that returns a generic array:

public static <T> T[] genericMethod1(List<T> input) {
    T[] res = (T[]) new Object[input.size()];

    int i = 0;
    for (T t : input) {
        res[i] = t;
        i++;
    }
    return res;
}

public static <T> T genericMethod2(List<T> input) {
    return input.get(0);
}

But later when I try to get the result array with:

LinkedList<Integer> list = new LinkedList<Integer>();
list.addFirst(1);
list.addFirst(1);

Integer[] i = (Integer[]) genericMethod1(list);  // 1) Runtime error
Integer j = genericMethod2(list);        // 2) works

For case 1, I always get error at runtime:

Exception in thread "main" java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [Ljava.lang.Integer;

Anybody can explain why and how to return the generic array properly? Thanks.

Below is my understanding, please correct me if I'm wrong.

As Tim mentioned, type erasure happened at compile time, so in bytecode, each T object is just type Object, meanwhile, compiler will add type cast from Object to T "properly".

Say T is an Integer, where T is declared, it's Object. For where it's referred, it's type cast (implicitly) to T.

EXCEPT that if T[] array is declared, it's Object[], and where the array is referred, it stays Object[]. No implicit cast to T[] happens.

2
  • Generics does not retain type at run-time. Commented Jan 22, 2016 at 5:28
  • To summarize here, type erasure removes the type, so if you want to return a generic array of a specific type, you will need to pass in that type. Commented Jan 22, 2016 at 6:10

4 Answers 4

26

The explanation for what you are seeing is due to something called type erasure. Here is what your genericMethod() will look like after the compiler performs type erasure:

public static Object[] genericMethod(List input) {
    Object[] res = new Object[input.size()];

    int i = 0;
    for (Object t : input) {
        res[i] = t;
        i++;
    }
    return res;
}

In other words, this method will return an array of type Object. There is no way to cast an Object[] to an Integer[] because they are not the same type. If you want your method to be able to dynamically return the type you want, then you can use Array.newInstance(). This will require also passing in the type of the array you want as an input parameter:

public static <T> T[] genericMethod(Class<T> clazz, List<T> input) {
    @SuppressWarnings("unchecked")
    T[] res = (T[]) Array.newInstance(clazz, input.size());

    int i = 0;
    for (T t : input) {
        res[i] = t;
        i++;
    }
    return res;
}

Now your code snippet will run without error:

LinkedList<Integer> list = new LinkedList<Integer>();    
Integer[] i = genericMethod(Integer.class, list);

Update:

Your second method, genericMethod2(), will look like this after type erasure:

public static Object genericMethod2(List input) {
    return input.get(0);
}

It will return the first element of the input list, cast to Object. Here is your usage of that method:

Integer j = genericMethod2(list);

The compiler will try to cast the output from genericMethod2() to Integer:

Integer j = (Integer)genericMethod2(list);

This cast is legal, because every Integer is also an Object, and furthermore it succeeds here because you passed in a collection of Integer. This second method is not the same scenario as the first one you highlighted for us.

Sign up to request clarification or add additional context in comments.

3 Comments

@Tom Biegeleisen Thanks for your comment. But I guess another reason is that Type Erasure and implicit type cast is not suitable for array type. Please see my modified question.
OK I will update my answer. By the way, Tom Biegeleisen is my brother.
Tim and Tom. Sounds like a movie:-)
4

When calling the method, genericMethod you are assuming that it returns array of integers, which is NOT correct. It actually returns array of type Object at runtime.

    List<Integer> input = new ArrayList<Integer>();
    input.add(1);
    Object[] output = genericMethod(input);
    for(Object obj : output){
        System.out.println("Value= "+ (Integer)obj);
    }

So we need to cast the individual content of the array.

One general guidline is that we shouldn't mix ARRAY and GENERICS in Java.

Update:

Reference from Effective Java:

In Summary, arrays and generics have very different type rules. Arrays are covariant and reified; generics are invariant and erased. As a consequcne, arrays provide runtime type safety but not compile-time type safety and vice versa for generics. Generally speaking, arrays and generics don’t mix well. If you find yourself mixing them and getting compile-time error or warnings, your first impulse should be to replace the arrays with lists.

Comments

0

Another way is to do it like in java.util.ArrayList.toArray(T[]). You pass the type of Array to that Method, if it's big enough it will be reused, otherwise an new Array is generated.

Example:

    List<Integer> intList = new ArrayList<>();
    intList.add(Integer.valueOf(1));
    intList.add(Integer.valueOf(2));
    intList.add(Integer.valueOf(3));
    Integer[] array = intList.toArray(new Integer[] {});
    System.out.println(Arrays.toString(array));//Will Print [1, 2, 3]

Implementation of ArrayList.toArray(T[]) see here.

Comments

0

After Java 8 was released, you can leverage constructor references to return a generic array. While there are more straightforward options to convert List to Integer[], I am explaining the concept here with kinda minimal changes to your code:

  • I am not touching your genericMethod2() implementation
  • For genericMethod1, let's add a second parameter that will accept a constructor reference. Then you can call the apply() function on the constructor reference to create a generic array.
    • you pass Integer[]::new to this new parameter. Integer[]::new is treated as (int x) => new Integer[x]
    • fi.apply(input.size()) calls (int x) => new Integer[x] with argument input.size(). The result is a Integer[]. This is why the IntFunction parameter has generic type T[]

import java.util.List;
import java.util.function.IntFunction;
import java.util.LinkedList;

public class RetGenericArr {
  public static <T> T[] genericMethod1(List<T> input, IntFunction<T[]> fi) {
    T[] res = fi.apply(input.size());

    int i = 0;
    for (T t : input) {
      res[i] = t;
      i++;
    }
    return res;
  }

  public static <T> T genericMethod2(List<T> input) {
    return input.get(0);
  }

  public static void main(String[] args) {
    LinkedList<Integer> list = new LinkedList<Integer>();
    list.addFirst(1);
    list.addFirst(2);

    // umm I am uncomfortable about the variable naming below:
    Integer[] i = genericMethod1(list, Integer[]::new); // Now no error/warning. Note that I am not casting now
    for (int e: i) {
      System.out.println(e);
    }

    Integer j = genericMethod2(list);
  }
}

Comments

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.