Oliver said:
[...]
What assumption? I'm "assuming" that I can put Objects in a List. What
is the difference at compile time between a List and a List<Object> (aside
from warnings, which is the crux of the issue here)? How is it any
different from not being able to check that List.add() vs.
List<Object>.add() is being used correctly?
The difference between a List and a List<Object> is that you don't know
what you can put in a List, while you know that you can put objects in
List<Object>.
A little more precision might help. You know that in principle you can
_safely_ put *some kind* of object in a raw List, but you don't a priori
know *which* kind. You therefore do indeed make an assumption when you
put any kind of object in such a list. On the other hand, you know that
you can put Objects in a List said:
I see now that the big problem here is that Java 1.5, while
syntactically backwards compatible with 1.4, is not nescessarily
"conceptually" backwards compatible as illustrated by this misunderstanding.
I disagree. I think the issue is more that the type safety problem was
so pervasive before 1.5 that many people formed incorrect
conceptualizations. If I create a List that I intend to hold only
Strings, then it should be regarded as a List<String> whether or not I
have generics available with which to express the concept. It is
certainly NOT a List<Object>, even though the compiler will not prevent
me from accidentally treating it as one. Careful programmers writing
without generics will document this sort of thing very clearly, which
has many of the effects of generic declarations with regard to other
humans reading the code. From a code analysis point of view, however,
you cannot tell from the declaration what type an instance may contain,
nor what types it is safe to add. This is precisely the situation that
generics are intended to clear up.
[Oliver wrote:]
Quite right. And if I take your List of Strings and treat it as a List
of Objects, then I might add an Integer to it. Or a plain Object. When
you come back to check the length() of each member your program will bomb.
Yes, a raw List is the same thing in 1.4 and 1.5. It is the only kind
available before 1.5. Because it wouldn't have made any sense for the
compiler to issue type safety warnings before 1.5, a raw List in 1.4 may
seem as if it should correspond to a List<Object>, but that's not the
case. When you write List<Object> you are asserting that it is safe to
add any object the list, but this is in fact rarely the case, whether in
1.4 or in 1.5, and indeed it was not the case in the OP's example. The
raw List type corresponds most closely to List<?>, the main difference
being that in the latter case you are explicitly stating that you don't
know what type you can safely add to or expect to remove from the List,
whereas in the former case that's implicit.
I hadn't thought about it earlier, but now that you've brought it to my
attention, I would say that a List in 1.4 is not equivalent ot a List in
1.5. Rather, a List in 1.4 is equivalent ot a List<Object> in 1.5. And a
List in 1.5 has no equivalent in 1.4, (nor for that matter does a List<Foo>
in 1.5 have an equivalent in 1.4).
Nope. You were going strong up to there, Oliver, but you just jumped
the track. Raw Lists are equivalent between 1.4 and 1.5 from a class
file standpoint, for one thing, but overall it amounts to what you can
prove about types. In that sense, as I described above, the raw List
type is most like List<?>.
[...]
when the
programmer performs a downward cast (e.g. from Object to String), if the
assumption the programmer made turns out to be incorrect, a runtime
exception will be thrown pointing precisely at this line.
However, in the case of generics, the type of the generic is erased at
runtime, and so no checks can be performed.
That's perhaps a bit misleading. Type parameters are indeed subject to
erasure, but an object always has a known, well-defined class, and
downcasts can always be checked. It's just that the type system in Java
1.5 has a dimension (type parameters) that the runtime ignores.
That means if the assumption the
programmer made turns out to be incorrect, no exception will be thrown at
all, and the program will simply behave in an undefined manner. This
certainly warrants a warning IMO.
You make it sound as if there were a security problem with 1.5 JVMs
here, and that simply is not the case. Yes, a cast from List<Object> to
List<String> cannot by typechecked at runtime (in fact, as you say,
there isn't actually a runtime cast at all), but when I take an object
out and try to assign it to a String variable or invoke a String method
on it then you can be sure that a ClassCastException will be thrown.
I agree that the warning is merited, given that one of the stated goals
for generics was to enable programmers to assure type safety, but
nothing catastrophic will happen if the warning is ignored and some
particular cast happens to be incorrect.