Iterate a parameterized list (after assigning the type to the original list)

Language: Java
Compiler Version: 1.6

In the code below I try to do the following:

  • create List<String>
  • add String
  • assign List<String> raw List
  • create List<Integer>
  • assign raw List List<Integer>
  • add Integer
  • get the value using get() @ indices 1 and 2 and print them.

All instructions are compiled (with warnings) and work fine.

But if I try to execute a List<Integer> loop with a for loop, I get a ClassCastException . I'm just wondering why this allowed me to use the list.get() method, but not let me list.get() over it?

Exit: (if I started the loop without comment) abcd 200

 Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer at genericsamples.CheckRawTypeAdd.main(CheckRawTypeAdd.java:26) 

Here is my code

 import java.util.*; import java.io.*; class CheckRawTypeAdd { public static void main(String[] sr) { List<String> list_str = new ArrayList<String>(); list_str.add("abcd"); List<Integer> list_int = new ArrayList<Integer>(); List list_raw; list_raw=list_str; list_int=list_raw; list_int.add(200); Object o1 = list_int.get(0); Object o2 = list_int.get(1); System.out.println(o1); System.out.println(o2); //for(Object o : list_int) //{ // System.out.println("o value is"+o); //} } } 
+8
java generics foreach classcastexception
source share
2 answers

I would consider this compiler error in javac . The checked listing rises. We can see this using javap -c CheckRawTypeAdd to parse the class (cast is 101; note that before compiling I extracted some unnecessary lines of code, so the code points will change):

  77: invokeinterface #10, 1 // InterfaceMethod java/util/List.iterator:()Ljava/util/Iterator; 82: astore 6 84: aload 6 86: invokeinterface #11, 1 // InterfaceMethod java/util/Iterator.hasNext:()Z 91: ifeq 109 94: aload 6 96: invokeinterface #12, 1 // InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object; 101: checkcast #13 // class java/lang/Integer 

However, the Java Language Spec (14.14.2) indicates that this cast should be an Object , not an Integer . It begins by defining terms through grammar:

 EnhancedForStatement: for ( FormalParameter : Expression ) Statement FormalParameter: VariableModifiersopt Type VariableDeclaratorId VariableDeclaratorId: Identifier VariableDeclaratorId [] 

So, in our case, Type is Object . Then he goes on to say what this means:

 for (I #i = Expression.iterator(); #i.hasNext(); ) { VariableModifiersopt TargetType Identifier = (TargetType) #i.next(); Statement } 

So the permission of TargetType appropriate here. This is also defined in JLS:

If the type (in the form of FormalParameter) is a reference type, then TargetType is Type

Since Object is most likely a reference type, then TargetType is Object , and so the checked listing should be Object , not Integer .

That this is a mistake is confirmed again by others in this thread, noting that this problem does not occur if ecj (Eclipse compiler) is used. However, I understand that this will be a low-priority error for the Oracle compiler command, since you must abuse generics to use it. One could almost say that this is a feature, not a mistake.

Subsequent

To give definitive confirmation that this is an error, there is an error report for this exact problem:

In addition, I must note two things. First , the JLS links above were in the last JLS, and this section has actually changed for Java 7 (in response to this error!)

Here's what advanced operator should translate to for Java 6 and earlier:

 for (I #i = Expression.iterator(); #i.hasNext(); ) { VariableModifiersopt Type Identifier = #i.next(); Statement } 

As you can see, test casting is not indicated here. Thus, the error in javac was not that he did the wrong translation, but that he performed any cast .

Second , in Java 7, javac correctly compiles code in accordance with the JLS SE 7 specification (this is what I cited above). Thus, the following code works:

 List<String> list_str = new ArrayList<String>(); ((List) list_str).add(new StringBuilder(" stringbuilder")); for (CharSequence o : list_str) { System.out.println("o value is" + o); } 

When pressed CharSequence , CharSequence , not String . I used JDK 6 first, not JDK 7.

+5
source share

The code

  for(Object o : list_int) { System.out.println("o value is"+o); } 

This is equivalent to something like this:

 for (Iterator<Integer> it = list.iterator(); it.hasNext();) { Integer o = it.next(); System.out.println("o value is"+o); } 

As you can see, Iterator is general and, therefore, distinguishes values ​​to its type of parameter ( Integer our case).

So, the line Integer o = it.next(); behind the scenes does the following:

Integer o = (Integer)it.next();

I think it’s now obvious how a ClassCastException is ClassCastException . Indeed, you managed to insert the string value into the list, so when it.next() returns your string, casting fails.

So, the question "how did you manage to insert the string into the int list" still remains. The answer is that generics is the magic of a compiler. Their other name is erasure. Java bytecode does not contain list type information. It simply contains casting to a specific type when necessary. It is for this reason that you managed to assign the parameterized list to the raw list, and then add sting to it.

As you said correctly, you saw warnings. Conclusion: "Do not ignore compilation warnings."

+1
source share

All Articles