2

I'm very new to generic programming in Java.

I don't understand why arrays of generic type can't be created.

T[] a = new T[size]; //why does this give an error?

If generic type means that the generic placeholder Twill be replaced by a class name during run-time, what prevents us from creating an array having generic references?

After a bit of searching, I found a workaround

T[] a = (T[])new Object[size];   //I don't get how this works?

Although I found a solution, I still fail to understand what prevents from creating a generic array. Suppose I create a function that returns an Object array.

public Object[] foo(){
        return new Object[12];
}

And then make the call

String[] a = (String[])foo();

gives a ClassCastException . But why? Doesn't it look similar to first line of code where I cast Object array into T array?

 T[] a = (T[])new Object[size];

If this went without a glitch why didn't that?

John Strood
  • 1,391
  • 2
  • 22
  • 35

3 Answers3

4

Part of the point is to look at it the other way around. You cannot do (String[]) new Object[10]; because an Object array is not a String array. Because

String[] array = new String[10];
array[0] = "foo";
String foo = array[0];

is fine, but

Object[] objectArray = new Object[10];
objectArray[0] = 10;
String[] stringArray = (String[]) objectArray;
String foo = stringArray[0];

...is trying to assign an Integer to a String, which shouldn't be allowed in the first place. So this code fails when you cast the Object[] to a String[]. That code has to throw a ClassCastException somewhere.

This is all the same for Java even before generics were invented in the first place. Accept all that first. Then move on to generics.

Now, the way Java generics are implemented means that when you compile the code, T is silently rewritten to Object. So T[] array = (T[]) new Object[10] is silently allowed, because it actually gets rewritten to Object[] array = new Object[10]. But as soon as you take it out, things go wrong. For example,

private static <T> T[] newArray() {
   return (T[]) new Object[10];
}

if you call String[] array = newArray(), you'll get a ClassCastException at the call site, not within newArray(). This is why Java gives you a warning at (T[]) new Object[10], and that warning may well lead to a real ClassCastException later on.

Generally speaking, don't mix arrays and generics. The way around all this is to use a List properly.

Louis Wasserman
  • 172,699
  • 23
  • 307
  • 375
  • Your answer was helpful but if the `T` is rewritten to `Object` then when I create an instance what happens? Like `Hello = new Hello<>()` Does the `T[]` remain `Object[]` only and not become `String[]`? – John Strood Sep 15 '15 at 16:13
  • Yes. Yes, the `T[]` remains `Object[]` only, and then when you try to get it out as a `String[]` you get a `ClassCastException`. Java will insert typecasts anywhere you pull out generics and assign them to an actual type. – Louis Wasserman Sep 15 '15 at 16:14
  • So if at compile time the generic placeholders are rewritten to `Object` then saying `T[] = new T[size]` should not be a problem because it will be converted into `Object[] = new Object[size]`. But yet the compiler gives an error? – John Strood Sep 15 '15 at 16:24
  • No, you can't write `new T[size]` at all, because the compiler was written to force you to create arrays only with their real actual type, so you can write `(T[]) new Object[n]`, but then you get a warning. The Java compiler doesn't let you pretend that `new T[]` is a real thing you can do. – Louis Wasserman Sep 15 '15 at 16:25
  • This is exactly what is haunting me for long long time. I would be very happy if you clarify this. ".. because the compiler was written to force you to create arrays only with their real actual type" What is the very need to write the compiler this way? What could go wrong in converting T[size] to Object[size]. – Ankush G Feb 15 '18 at 16:22
  • @vvtx that's covered in the second part of the answer. – Louis Wasserman Feb 15 '18 at 16:26
3

There are several things to note when dealing with arrays.

First, arrays are considered to be covariant; that is, a typed array will maintain its inheritance chain. So, an Integer[] is an Object[] in the same fashion that an Integer is an Object.

This is why your last example fails. You want to cast an Object[] to a String[] through foo:

String[] a = (String[])foo();

An Object[] will never be a String[] since an Object isn't a String (but the opposite will always be true).

Second, arrays and generics don't mix all that well. Generics are considered to be invariant; that is, they would not maintain their inheritance chains. A List<Integer> is not considered to be the same as a List<Object>.

As to why your particular example fails, this is due to type erasure at compile time. Arrays are required to know their concrete type at compile time, and without this information, they cannot be instantiated. Since generics don't store that information, you can't instantiate a generic array in the same way you would instantiate a non-generic array.

That is to say, you must use the cast form:

T[] a = (T[]) new Object[size];

You can read a bit more about generic arrays in this answer, as it covers most of the main points that you would need to know when dealing with them.

Community
  • 1
  • 1
Makoto
  • 96,408
  • 24
  • 164
  • 210
  • You said: "...required to know their concrete type at compile time, and without this information, they cannot be instantiated" But instantiation does not happen at Compile time but the runtime and at run time we do know the concrete type for type parameter and then at runtime it can then instantiate the array of that concrete type. Please correct me if I am wrong. – Ankush G Feb 15 '18 at 16:51
  • @vvtx: Yeah, you're wrong. You can't reify a generic parameter. Arrays require reifiability. – Makoto Feb 15 '18 at 16:56
  • Yes I learned that Arrays require reifiability, but that is required while instantiation, i.e. at run time, and at run time you know your concrete type. You said: "You can't reify a generic parameter" but when the parameter type is non array it is reified to Object type in byecode, i.e. T e is replaced to Object e after compilation then what is stopping T[] e to Object[] e? – Ankush G Feb 15 '18 at 17:06
0

Arrays know their type at runtime. A String[] knows it is an array of Strings.

In contrast to this, generic type parameters are erased at runtime, so a List<String> at runtime is just a List.

Since type parameters T are not available at runtime new T[10] (which doesn't compile) could not possibly create a true T[].

It is not true that

T[] a = (T[])new Object[size];

can't throw an exception. It can. Louis Wasserman's example shows that it can cause an exception at the call site, but that line can also throw an exception directly. For example

public static void main(String[] args) {
    foo();
}

static <T extends Number> void foo() {
    T[] array = (T[]) new Object[42];
}

Here, the lower bound of T is Number, so at runtime, it is attempted to cast an Object[] to a Number[], which throws a ClassCastException.

You can create a T[] if you have a Class<T> object clazz, using for example

Array.newInstance(clazz, length);
Paul Boddington
  • 35,031
  • 9
  • 56
  • 107