Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to properly return generic array in Java generic method?

Tags:

java

generics

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.

like image 985
Bill Randerson Avatar asked Jan 22 '16 05:01

Bill Randerson


People also ask

Can we use generics with array in Java?

Java allows generic classes, methods, etc. that can be declared independent of types. However, Java does not allow the array to be generic. The reason for this is that in Java, arrays contain information related to their components and this information is used to allocate memory at runtime.

Can you instantiate generic arrays?

Although we cannot instantiate a generic array of a specific type parameter, we can pass an already created array to a generic class constructor.


1 Answers

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.

like image 184
Tim Biegeleisen Avatar answered Nov 05 '22 07:11

Tim Biegeleisen