Why doesn't line 4 generate an unchecked exception?
/*1.*/ List l = new ArrayList<Number>();
/*2.*/ List<String> ls = l; // unchecked warning
/*3.*/ l.add(0, new Integer(42)); // another unchecked warning
/*4.*/ String s = ls.get(0);
If the lines 2 and 3 produce an unchecked warning then why doesn't t开发者_JAVA技巧he 4th line generate a unchecked warning since compiler does not know what 'ls' is referring to (List<String>
or List<Integer>
).
(Note: edited from the OP's original post to make the code display as presumably intended - notably include the type parameters for List<E>
everywhere.)
The compiler thinks that ls
will genuinely refer to a list of strings - line 4 would be "safe" (in terms of type safety) if there hadn't already been dangerous operations - namely line 2.
With no other warnings involved, a List<String>
should always end referring to a list which only contains references to strings (or null). But if you've already broken type safety, all bets are off (other than that the VM will catch those type violations at execution time).
The warning shows which lines are dangerous - in this case, places where you're using a raw type, i.e. you're saying, "Well, we've got a list here. I've no idea what's in it though."
Line 4 doesn't refer to any variables of raw types - it only refers to calling get
on a List<String>
, and assigning the return value to a String
variable. If you think this should produce a warning, please show which section of the Java Language Specification suggests that a warning is appropriate - I think you'll find it hard to do so.
Line 4 produces no compile-time warning because the type of ls
is List<String>
which means its get
methods returns a String
or null
. Any violation will be caught at compile time and will result in a ClassCastException
.
EDIT:
There are a number of things that the compiler knows about that the bytecode verifier and interpreter do not.
- generic types
- compile-time-only annotations
- checked exceptions
- outer-class privates
The java compiler knows about these things and so points out errors in their use, but the bytecode verifier and interpreter do not so it "erases" them when producing the portion of the .class
file meant for the interpreter.
So the steps in compilation are
- Look for all the classes needed by looking for
.java
and.class
files. - Make sure everything type checks.
- Erase information not needed by the interpreter (but hide it awway in case javac gets called with the
.class
on its input CLASSPATH). - Produce the output
.class
files.
Line 4 doesn't give a warning because ls
is declared as a List<String>
, the ls.get(0)
returns a String (as far as the compiler knows), and you're assigning it to a String variable s
To the compiler, everything looks correct - it does not look at the fact that you "illegally" pointed it at a List<Integer>
.
You will, however, get a ClassCastException are runtime.
Line 4 is absolutely fine. You are probably thinking your code is :
String s = l.get(0); // this will not compile without the cast
Line 4 does not warrant an unchecked warning, as it does not introduce the potential of type errors. The declaration of ls
says it's a List<String>
, and List<String>
contain String
s. That it does not is a pathological situation called heap pollution that can arise only if unchecked warnings are incorrectly suppressed, but that's not the fault of line 4.
Line 4 does, however, throw a ClassCastException
at runtime, as the compiler is aware of the possiblity of heap pollution, and emits an additional cast to verify that the returned object is of the proper type before invoking methods on it.
精彩评论