2

I know that generic type information is erased at compile time in Java and therefore one cannot create arrays of generic types (because then at runtime it would not be possible to effectively enforce the types inserted into the array).

But why not make an exception? Why not just leave the generic type information for arrays (and only for them)?

What was the design decision behind this? In my opinion it would make life easier, it would be possible to do this.

T[] genericArray = new T[10];
PaperTsar
  • 725
  • 6
  • 22
  • Backward compatibility probably? – Codebender Jul 10 '15 at 08:04
  • This is probably a duplicate. – Raedwald Jul 10 '15 at 08:17
  • @BendeguzNagy I am not sure what advantage you would get from saying `T[] genericArray = new T[10];` vs using `Object[] genericArray = new Object[10]` in case of arrays. The type parameter is pretty much useless (unless you are expecting that once you add an element to `T[] genericArray `, the compiler should deduce the type and not let you added elements that are not of the type of the first element inserted? Ofcourse that's not possible and too much to ask for) – CKing Jul 10 '15 at 08:18

2 Answers2

5

Short answer:

It's because generics is metadata to help the compiler help you catch type errors and everything is compiled to use the lowest common denominator (usually Object) and type casts. This isn't done with arrays since arrays are their own classes. I.e. An ArrayList<String> and an ArrayList<Number> both have the class ArrayList, but an array of String has class String[] and an array of Number has class Number[].

Long answer:

When compiled, everything that is using generics will be using the least common denominator (which is commonly Object). This is demonstrated by the following code:

public class Generics {

    public static <T> void print(T what) {
        System.out.println(what);
    }

    public static <T extends Number> void printNumber(T what) {
        System.out.println(what);
    }

    public static void main(String[] args) {
        Arrays.stream(Generics.class.getDeclaredMethods())
                .filter(m -> m.getName().startsWith("print"))
                .forEach(Generics::print);
    }

}

This prints:

public static void Generics.print(java.lang.Object)
public static void Generics.printNumber(java.lang.Number)

So we can see that when it's compiled it is compiled as methods that works on Object and Number respectively.

This is the reason something like this will compile and run:

ArrayList<String> list = new ArrayList<>();
list.add("foo");
ArrayList<Object> list2 = (ArrayList<Object>)(Object)list;
list2.add(Integer.valueOf(10));
System.out.println(list2.get(0));
System.out.println(list2.get(1));

If you try that you'll see that it prints

foo
10

So by the down/up-cast we turned our ArrayList<String> into an ArrayList<Object> - which wouldn't be possible if the ArrayList was actually storing it's contents in an array of type String[] instead of Object[].

Note that trying to do

System.out.println(list.get(0));
System.out.println(list.get(1));

Will result in a ClassCastException. And this hints at exactly what the compiler does.

Look at the following code:

public static void doThingsWithList() {
    ArrayList<String> list = new ArrayList<>();
    list.add("");
    String s = list.get(0);
    print(s);
}

When compiled, it's turned into this bytecode:

public static void doThingsWithList();
  Code:
     0: new           #11                 // class java/util/ArrayList
     3: dup
     4: invokespecial #12                 // Method java/util/ArrayList."<init>":()V
     7: astore_0
     8: aload_0
     9: ldc           #13                 // String
    11: invokevirtual #14                 // Method java/util/ArrayList.add:(Ljava/lang/Object;)Z
    14: pop
    15: aload_0
    16: iconst_0
    17: invokevirtual #15                 // Method java/util/ArrayList.get:(I)Ljava/lang/Object;
    20: checkcast     #16                 // class java/lang/String
    23: astore_1
    24: aload_1
    25: invokestatic  #17                 // Method print:(Ljava/lang/Object;)V
    28: return

As you can see on line 20 the result from ArrayList.get is actually cast to String.

So generics is just syntactic sugar that turns into automatic type casting with the added benefit that the compiler can use this syntactic sugar to detect code that would result in a ClassCastException during runtime.

Now, why can't the compiler do the same for String[] and Object[]? Couldn't it just turn

public <T> T[] addToNewArrayAndPrint(T item) {
    T[] array = new T[10];
    array[0] = item;
    System.out.println(array[0]);
    return array;
}

into

public <T> T[] addToNewArrayAndPrint(T item) {
    Object[] array = new Object[1];
    array[0] = item;
    System.out.println((T) array[0]);
    return array;
}

?

Nope. Because that would mean that

Arrays.equals(addToNewArray("foo"), new String[]{ "foo" });

would be false because the first array would have class Object[] and the second would have class String[].

Sure, Java could be changed so that all arrays are of type Object[] and that all accesses would use casts, just as when using generics. But that would break backwards compatibility whereas using Generics doesn't since an ArrayList<String> has the same class as an ArrayList.

Raniz
  • 10,048
  • 1
  • 28
  • 61
1

The problem with a construct like new T[10]; is that T could be anything, including Void (which is actually an uninstantiable class and would generate a compilation error).

If you think about it, types are a language construct and therefore should make its magic at compile time, rather than at runtime. There are situations in which it would make sense to have runtime type information and some languages actually implement it, but it is debatable whether you need it and is a "Good Thing™".

Some useful info about type erasure: https://stackoverflow.com/a/21843984/1417546

Community
  • 1
  • 1
EmirCalabuch
  • 4,445
  • 1
  • 21
  • 19
  • 2
    There is no problem with an array of `Void`, even at runtime. It's just another type: `Void[] a = new Void[1];` The type has the single member `null`. – Lii Jul 10 '15 at 08:38
  • When I said uninstantiable I referred to the fact that you cannot instantiate a `Void` object (`new Void()` rather than `new Void[]`, arrays of `Void`s are in fact acceptable). – EmirCalabuch Jul 10 '15 at 12:05
  • However I clarified the concept in my answer, and also I incorrectly stated that it generated a runtime error, and it actually produces a compile time error (`the constructor is not visible`). – EmirCalabuch Jul 10 '15 at 12:28