Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Generics and type erasure

Can anyone explain to me why this happens:

public class Array<E> {

    public E[] elements = (E[]) new Object[10];

    public E get(int idx) {
        return elements[idx]; // Ignore bound-checking for brevity.
    }

    public static void doSomething(Array<Integer> arr) {
        Integer good = arr.get(0);
        Integer bad1 = arr.elements[0];
        Integer bad2 = ((Integer[]) arr.elements)[0];
        Integer bad3 = (Integer) arr.elements[0];
        // `bad1', `bad2', and `bad3' all produce a 
        // runtime exception.
    }

    public static void main(String[] args) {
        Array<Integer> test = new Array<>();

        Array.doSomething(test);
    }
}

Full example here: http://pastebin.com/et7sGLGW

I've read about type erasure and realize type-checking is performed during compilation and then E is simply replaced with Object, so all we have is public Object[] elements, but why does the get method succeed where regular type-casting doesn't? Doesn't the get's method return type also get erased?

Thanks.

like image 352
d125q Avatar asked Apr 14 '26 05:04

d125q


1 Answers

Even though arr has type Array<Integer> (and arr.elements has type Integer[]), arr.elements actually has runtime-type Object[], because the actual array is an instance of type Object[].

(Note that arrays, unlike generics, are covariant, and do not have erasure. Object[] foo = new String[5]; is legal — as is String[] bar = (String[]) foo;. Whereas Integer[] baz = (Integer[]) foo; would raise a ClassCastException at runtime.)

So the reason that any reference to arr.elements triggers a runtime-exception is that the compiler automatically inserts a downcast to Integer[], to bring the type and the runtime-type back into accord. Inside the body of doSomething, arr.elements actually means (Integer[]) arr.elements, with an implicit cast.

By contrast, inside get(), the type of this is just Array<E>, so the type of elements is just E[], which cannot be checked. So the compiler does not insert any implicit casts.


The major take-home point is that (E[]) new Object[10]; is actually incorrect. new Object[10] does not create an instance of E[]. The compiler can't see that it's incorrect, but it will insert lots of casts that will see that it's incorrect.

A better approach is to use Object[] elements = new Object[], and to perform correct-but-unchecked casts from Object to E, when necessary, rather than incorrect-and-unchecked casts from Object[] to E[].

Do you see what I mean?

like image 100
ruakh Avatar answered Apr 16 '26 21:04

ruakh



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!