What could happen when one of the type parameters was not specified during instantiation of a collection?
The instantiation of a collection in Java is normally as below:
ArrayList<Integer> ali = new ArrayList<Integer>();
It is said that with this convention, certain errors such as
String s = (String)ali(0)
Can lead to compile error instead of run time ex开发者_运维百科ceptions.
However, I observed that although
ArrayList ali = new ArrayList<Integer>();
Will cause the situation above to cause run time exceptions,
ArrayList<Integer> ali = new ArrayList();
Will still cause compile time error in the situation above.
Is there something I miss, or could we ignore the type on the right hand side if we do not care for clarity of code?
Thanks!
ArrayList<Integer> ali = new ArrayList();
and
ArrayList ali = new ArrayList<Integer>();
This will generate a compiler warning re: unchecked conversion. You will only get the compile safety of Generics if you do not ignore these warnings, or Supress them with the annotation because you can prove it's safe.
You do a raise an interesting point with:
ArrayList<Integer> ali = new ArrayList();
As you will only be using ali you do have safety with the reference. However you'll have the Compiler warning for the right hand side of the expression, so it's best to add the parameterized type and keep the compiler free of warnings. The reason the compiler is warning you is because someone could come and do this:
ArrayList<String> strings = new ArrayList<String>();
ArrayList<Integer> integers = new ArrayList(strings);
Oh no you've now got Strings in your Integers!
This is where Java 7's type inference comes in i.e.
ArrayList<Integer> ali = new ArrayList<>();
So there will no longer be a need for the parameterized type to be specified, as Integer is inferred. You can do this in Java 5 or 6 by writing a generic method such as makeArrayList()
which infers the type (see Joshua Bloch Effective Java book)
You are right in your assessment that your last code snippet is not actually dangerous (though it does generate the compiler warning).
The reason why using raw types is potentially dangerous, is because you lose the type-safety that generics provides. More specifically, you lose the guarantee that you can't treat the " generic parameter as two different types in two different scenarios. (This is what the problem is in your casting-to-String example - the list is considered to contain integers at one point (when being populated) but considered to contain Strings at another point).
In the last example you've provided, the warning is technically spurious since the raw-typed list that's being constructed can only be referenced by the ali
reference, which is correctly typed. Therefore, it would be impossible to insert strings into it.
However, the compiler can't guarantee this in general, as it's an implementation detail of how the ArrayList
constructor works that makes this safe. (Another list implementation could "publish" a reference to itself externally, which could then be used to insert the wrong type of elements into this list). The compiler just sees that you're assigning something that's of the raw type ArrayList
to a variable of type ArrayList<Integer>
, and correctly says that "the thing on the right hand side might have been used for things other than Integers in the past, you know - are you sure this is OK?" It's roughly equivalent to
ArrayList al = new ArrayList();
ArrayList<Integer> ali = al;
where in this slightly expanded case, the "temporary" variable al
allows one to call al.add("not an int")
without compile time errors.
There's no real benefit to doing things this way and "knowing" it's correct, you may as well construct the list with the right generic parameters from the get-go, as in your first example. Unchecked conversion warnings are often not a real problem, but quite often can be - suppressing the warnings runs the risk that you'll migrate from the first situation to the second without noticing. Getting the compiler to check for you means it can tell you if your underlying assumptions become invalidated.
The compiler only checks if a variable that declares a generic type is used correct. If the variables type is a raw type, then it won't complain (with an error). So the following lines compile and run with no error:
ArrayList list = new ArrayList<Integer>();
list.add("hello");
String s = (String) list.get(0);
Note, that the compiler doesn't care that we used the generic constructor and that the runtime won't notice because of type erasure.
Once the variable has a generic type, then the compiler can check, if you use the variable "correctly" (like for collection: the compiler knows that get(0)
will return the generic type and can complain on illgal casts)
The last example is safe (in this case). It is critical, if the constructor uses some typed parameters.
The following lines show the problem:
ArrayList<Double> doubles = new ArrayList<Double>();
ArrayList<Integer> integers1 = new ArrayList<Integer>(doubles); // error
ArrayList<Integer> integers2 = new ArrayList(doubles); // no error
With the third line we can legally populate a Integer
typed array with Double
values, we just have to ignore the warning (and catch all runtime exceptions later ;) )
OT and Trivia
With Java 7 we get the diamond operator:
ArrayList<List<Integer>> multilist = new ArrayList<List<Integer>>(); // Java 1.5+
ArrayList<List<Integer>> multilist = new ArrayList<>(); // Java 7+
The problem is that
ArrayList ali = new ArrayList<Integer>();
is an untyped Collection. The compiler warns your about it with this warning message:
ArrayList is a raw type. References to generic type ArrayList<E> should be parameterized
However, since you have not typed it, the compiler can not know what types are in variable ali
.
At runtime, however the type is erased - you in effect have ArrayList<Object>
. When you retrieve an element (an Integer) and try to assign it to a String, it explodes, of course with a ClassCastException
If you don't care about code clarity, you could do it - but I don't see why you'd want to. There seems to be absolutely no gain (aside from saving a few characters worth of typing) in doing so, and it just makes your code slightly harder to read if you're not declaring and initialising on the same line.
By themselves, neither of these will produce runtime errors:
ArrayList ali = new ArrayList<Integer>();
or
ArrayList<Integer> ali = new ArrayList();
However, that's only because you've not tried to populate the list. If you do, and you make a mistake, you can get unexpected ClassCastException
s when using values extracted from the list. For example:
ArrayList ali = new ArrayList();
ali.add("Hi mum");
ArrayList<Integer> oop = ali; // unsafe conversion
Integer first = oop.get(0);
The last line won't give a compilation error or warning, but at runtime it will give a ClassCastException
. The CCE is thrown because the compiler does an implicit type cast as part of the assignment.
精彩评论