0

Why the following code is potentially not type-safe (the compiler generates a warning)?

class ArrayTypeErasure<T> {
    private T[] elements;

    public void setElements(List<T> elements) {
        this.elements = (T[]) elements.toArray();
    }
}

I'm trying to think of any situations when the code would fail, but so far it's only me who is failing.

super.t
  • 1,892
  • 1
  • 19
  • 37

3 Answers3

2

Simply because List#toArray() returns an Object[] so there is no guarantee from this method that it would return T[]

Now, in practice, it's ok since you always know that it will return the wanted type

You could use @SuppressWarnings("unchecked") to avoid this warning from appearing

Yassin Hajaj
  • 20,020
  • 9
  • 41
  • 81
  • 1
    Closing questions as duplicates doesn't hurt, there is no need to answer such quesions again. – Tom Mar 06 '19 at 14:16
  • so you mean the `elements` array can somehow be rewritten, say from `List[]` originally to `List[]`, but the passed list would still contain elements of `List[]`? – super.t Mar 07 '19 at 06:32
2

First, note that arrays know their component type at runtime, but instances of generic classes don't know the generic type argument at runtime. A List<T> cannot create an array object at runtime with the runtime type of T[] without further information, since it doesn't know what T is at runtime. The List#toArray() method that takes one array parameter, uses the runtime type of the passed-in array instance to construct an array of the same component type at runtime. But the List#toArray() with no parameters always creates an array with the runtime type Object[]. So elements.toArray() evaluates to an array instance that always has the runtime type of Object[].

Object[] is not a subtype of T[] (when T is not Object), so assigning this array to this.elements, of compile-time type T[], is wrong. However, it doesn't immediately cause any exceptions, since the erasure of T is Object, so the erasure of T[] is Object[], and assigning Object[] to Object[] is fine. It won't cause any issues as long as you make sure to never expose the object contained in this.elements to outside this object as T[]. However, if you expose the object contained in this.elements as type T[] to outside the class (e.g. a method that returns this.elements as type T[], or if you make this.elements a public or protected field), a caller outside the class might expect T to be a specific type, and that can cause a class cast exception.

For example, if you have a method that returns this.elements as type T[]:

public T[] getElements() {
    return this.elements;
}

and then you have a caller that holds ArrayTypeErasure<String> and it calls .getElements() on it, it will expect a String[]. When it tries to assign the result to a String[], it will cause a class cast exception, since the runtime type of the object is Object[]:

ArrayTypeErasure<String> foo = ...
String[] bar = foo.getElements();
newacct
  • 110,405
  • 27
  • 152
  • 217
-2

Due to type erasure, you can cast List<X>s to List<Y>s.

ArrayTypeErasure<String> er = new ArrayTypeErasure<>();
ArrayTypeErasure erased = er;
List intList = new List<Integer>();  // compile warnings, but
intList.add(1);
erased.setElements(intList);
daniu
  • 12,131
  • 3
  • 23
  • 46
  • 1
    Fooling around with raw types is no real argument here. Its like saying "everything in Java is broken and insecure, because reflection exists". – Tom Mar 06 '19 at 14:17
  • @Tom He asked for a situation in which it failed, I provided one. Both generic array creation and raw types are rooted in type erasure, so there's no reason not to bring it up. – daniu Mar 06 '19 at 14:20
  • You're right that this can be an issue, but of a different scope and compilers (or IDEs) don't generate warnings for the usage of generic types, because someone could ignore them. Thus this doesn't explain why OP still gets a warning for `(T[]) elements.toArray()` and if that really could fail. And your example doesn't explain how the array creation could fail. Your code fails _after_ the array was created and then wrongly used, by deactivating type checks. – Tom Mar 06 '19 at 14:30
  • @Tom `List#toArray`works fine, the array creation will never fail. It's always the cast that is the issue here. – daniu Mar 06 '19 at 14:38