Should this code not throw a ClassCastException

The following code compiles and runs without any exceptions.

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(); } } } 

do not follow the line ArrayList<SuperSample> ssList = (ArrayList<SuperSample>)o; ClassCastException ?

while the following code generates a compilation error to avoid heap contamination, should the code mentioned above have the same preventive measures at runtime?

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

EDIT:

If the cause is Erasure type erasure, should there be additional mechanisms to prevent adding an invalid object to the list? eg

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

then why

 ssList.add(new SuperSample()); 

not done to throw any exception?

+8
java generics classcastexception
source share
5 answers

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 a ClassCastException on the last line?

Not. This exception is thrown by the JVM when it detects incompatible types that are run at run time. As others have noted, this is due to the erasure of common types. That is, common types are known only to the compiler. At the JVM level, variables are of type ArrayList (generated styles), so there is no ClassCastException .

Instead of assigning an intermediate local variable of type Object , a shorter way to do this assignment is to pass through raw:

  ArrayList<SuperSample> ssList = (ArrayList)sList; 

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

Should there be additional mechanisms to prevent adding an invalid object to the list?

Yes there is. The first mechanism is checking compilation time. In your own answer, you found the right place in the Java Language Specification, where it describes heap pollution , which is the term for an invalid object that has a place in the list. Quote from this section, bottom to bottom,

If an operation that requires an immediate compilation warning is not performed, and there is no unsafe smoothing of array variables with unrecoverable element types, then heap contamination cannot occur.

So, the mechanism you are looking for is in the compiler, and the compiler notifies you of this through compilation warnings. However, you disabled this mechanism using the @SuppressWarnings annotation. If you want to remove this annotation, you will receive a compiler warning on the violation line. If you absolutely want to prevent heap pollution, do not use @SuppressWarnings and add the -Xlint:unchecked -Werror options to your javac command line.

The second mechanism is runtime verification, which requires the use of one of the tested shells. Replace sList initialization as follows:

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

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

+1
source share

No, this should not be, at run time, both lists have the same ArrayList type. This is called erasure . General parameters are not part of the compiled class; they are all deleted at compile time. From a JVM perspective, your code is:

 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(); } } 

In general, generics only simplify development by compiling time errors and warnings, but they do not affect execution at all.

EDIT:

Well, the basic concept of this Reifiable Type . Id highly recommend reading this guide:

A type being acknowledged is a type whose type information is fully accessible at run time. This includes primitives, not generic types, raw types, and calls to unrelated wildcards.

Non-recoverable types are types in which information is deleted by compiling time by type of erasure.

In short: arrays are reminerable, but general collections are not. Therefore, when you store smth in an array, the type is checked by the JVM because the type of the array is present at runtime. An array is just part of a memmory, and a collection is a regular class that can have some kind of implementation. For example, it can store data in db or on disk under the hood. If you want to go deeper, I suggest reading the Java Generics and Collections book .

+6
source share

The key is here to answer your question: Erasure type in java

You have a warning at compile time for your first case, and not in the second because of your indirection by an object that prevents the compiler from raising a warning for you (I assume that this warning occurs when a parameterized type is issued to another one that fails in your second if anyone can confirm that I would be happy here about this). And your code runs because at the end of sList ssList et o all ArrayList

+1
source share

I think this error cannot throw a ClassCastException due to backward compatibility issue in Java.

General information is not included in the bytecode (the compiler receives it due to compilation).

Imagine the script that you use in your project, the old code for obsolete code (some old library written in java 1.4), and you pass the general list to some method in this obsolete code. You can do it.

During the time preceding the outdated generic code, it was allowed to put anything at all (except for primitives) in the collection. Thus, this legacy code cannot get a ClassCastException, even if it tries to put a String in a List <Integer>. In terms of legacy code, it's just List.

Thus, this strange behavior is the result of type erasure and backward compatibility in Java.

EDIT:

You get an ArrayStoreException for arrays because the JVM KNOWS runtime is an array type, and you get no exception for collections due to erasing styles and this backward compatibility problem. The JVM does not know the type of collection at runtime.

You can read this topic in the book, Sunju Certified Certified Java ™ 6 Study Guide SCJP Study Guide, in Chapter 7, Generics and Collections

+1
source share

From JLS (4.12.2)

It is possible that a variable of a parameterized type refers to an object that is not this parameterized type. This situation is known as heap contamination. This situation can only happen if the program has performed some operation that would lead to an unchecked warning at compile time.

For example, the code:

 List l = new ArrayList<Number>(); List<String> ls = l; // unchecked warning 

leads to an uncontrolled warning, because it is impossible to set, either at compilation, time (as part of compilation type checking rules) or at runtime, regardless of whether the variable l really refers to a List<String> . If the above code is executed, heap pollution occurs because the variable ls is declared as List<String> , refers to a value that is not actually List<String> . The problem cannot be identified at run time because type variables are not overridden, and therefore instances do not contain any information at run time regarding the actual type of parameters used to create them.

0
source share

All Articles