Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

shouldn't this code produce a ClassCastException

The following code compiles and runs successfully without any exception

import java.util.ArrayList;

class SuperSample {}

class Sample extends SuperSample {

  @SuppressWarnings("unchecked")
  public static void main(String[] args) {

    try {

      ArrayList<Sample> sList = new ArrayList<Sample>();
      Object o = sList;
      ArrayList<SuperSample> ssList = (ArrayList<SuperSample>)o;
      ssList.add(new SuperSample());

    } catch (Exception e) {
      e.printStackTrace();
    }

  }
}

shouldn't the line ArrayList<SuperSample> ssList = (ArrayList<SuperSample>)o; produce a ClassCastException ?

while the following code produces a compile time error error to prevent heap pollution, shouldn't the code mentioned above hold a similar prevention at runtime?

ArrayList<Sample> sList = new ArrayList<Sample>();
ArrayList<SuperSample> ssList = (ArrayList<SuperSample>) sList;

EDIT:

If Type Erasure is the reason behind this, shouldn't there be additional mechanisms to prevent an invalid object from being added to the List? for instance

String[] iArray = new String[5];
Object[] iObject = iArray;
iObject[0]= 5.5;  // throws ArrayStoreException

then why,

ssList.add(new SuperSample());

is not made to throw any Exception?

like image 841
Thirumalai Parthasarathi Avatar asked Nov 22 '13 11:11

Thirumalai Parthasarathi


2 Answers

No it should not, at run time both lists have the same type ArrayList. This is called erasure. Generic parameters are not part of compiled class, they all are erased during compilation. From JVM's perspective your code is equal to:

public static void main(String[] args) {
    try {
      ArrayList sList = new ArrayList();
      Object o = sList;
      ArrayList ssList = (ArrayList)o;
      ssList.add(new SuperSample());
    } catch (Exception e) {
      e.printStackTrace();
    }
}

Basically generics only simplify development, by producing compile time errors and warnings, but they don't affect execution at all.

EDIT:

Well, the base concept behind this is Reifiable Type. Id strongly recomend reading this manual:

A reifiable type is a type whose type information is fully available at runtime. This includes primitives, non-generic types, raw types, and invocations of unbound wildcards.

Non-reifiable types are types where information has been removed at compile-time by type erasure

To be short: arrays are rifiable and generic collections are not. So when you store smth in the array, type is checked by JVM, because array's type is present at runtime. Array represents just a piece of memmory, while collection is an ordinary class, which might have any sort of implementation. For example it can store data in db or on the disk under the hood. If you'd like to get deeper, I suggest reading Java Generics and Collections book.

like image 92
Mikhail Avatar answered Nov 20 '22 06:11

Mikhail


In your code example,

    class SuperSample { }
    class Sample extends SuperSample { }
    ...
    ArrayList<Sample> sList = new ArrayList<Sample>();
    Object o = sList;
    ArrayList<SuperSample> ssList = (ArrayList<SuperSample>)o;

Shouldn't the last line produce a ClassCastException?

No. That exception is thrown by the JVM when it detects incompatible types being cast at runtime. As others have noted, this is because of erasure of generic types. That is, generic types are known only to the compiler. At the JVM level, the variables are all of type ArrayList (the generics having been erased) so there is no ClassCastException at runtime.

As an aside, instead of assigning to an intermediate local variable of type Object, a more concise way to do this assignment is to cast through raw:

    ArrayList<SuperSample> ssList = (ArrayList)sList;

where a "raw" type is the erased version of a generic type.

Shouldn't there be additional mechanisms to prevent an invalid object from being added to the List?

Yes, there are. The first mechanism is compile-time checking. In your own answer you found the right location in the Java Language Specification where it describes heap pollution which is the term for an invalid object occurring in the list. The money quote from that section, way down at the bottom, is

If no operation that requires a compile-time unchecked warning to be issued takes place, and no unsafe aliasing occurs of array variables with non-reifiable element types, then heap pollution cannot occur.

So the mechanism you're looking for is in the compiler, and the compiler notifies you of this via compilation warnings. However, you've disabled this mechanism by using the @SuppressWarnings annotation. If you were to remove this annotation, you'd get a compiler warning at the offending line. If you absolutely want to prevent heap pollution, don't use @SuppressWarnings, and add the options -Xlint:unchecked -Werror to your javac command line.

The second mechanism is runtime checking, which requires use of one of the checked wrappers. Replace the initialization of sList with the following:

    List<Sample> sList = Collections.checkedList(new ArrayList<Sample>(), Sample.class);

This will cause a ClassCastException to be thrown at the point where a SuperSample is added to the list.

like image 1
Stuart Marks Avatar answered Nov 20 '22 06:11

Stuart Marks