For instantiations of two ArrayList
s, one with the diamond operator at the end and one without...
List<Integer> fooList = new ArrayList<>();
List<Integer> barList = new ArrayList();
...the bytecode generated is identical.
LOCALVARIABLE fooList Ljava/util/List; L1 L4 1
// signature Ljava/util/List<Ljava/lang/Integer;>;
// declaration: java.util.List<java.lang.Integer>
LOCALVARIABLE barList Ljava/util/List; L2 L4 2
// signature Ljava/util/List<Ljava/lang/Integer;>;
// declaration: java.util.List<java.lang.Integer>
So there wouldn't any difference between the two as per the bytecode.
However, the compiler will generate an unchecked warning if you use the second approach. Hence, there's really no value in the second approach; all you're doing is generating a false positive unchecked warning with the compiler that adds to the noise of the project.
I've managed to demonstrate a scenario in which doing this is actively harmful. The formal name for this is heap pollution. This is not something that you want to occur in your code base, and any time you see this sort of invocation, it should be removed.
Consider this class which extends some functionality of ArrayList
.
class Echo<T extends Number> extends ArrayList<T> {
public Echo() {
}
public Echo(Class<T> clazz) {
try {
this.add(clazz.newInstance());
} catch (InstantiationException | IllegalAccessException e) {
System.out.println("YOU WON'T SEE ME THROWN");
System.exit(-127);
}
}
}
Seems innocuous enough; you can add an instance of whatever your type bound is.
However, if we're playing around with raw types...there can be some unfortunate side effects to doing so.
final Echo<? super Number> oops = new Echo(ArrayList.class);
oops.add(2);
oops.add(3);
System.out.println(oops);
This prints [[], 2, 3]
instead of throwing any kind of exception. If we wanted to do an operation on all Integer
s in this list, we'd run into a ClassCastException
, thanks to that delightful ArrayList.class
invocation.
Of course, all of that could be avoided if the diamond operator were added, which would guarantee that we wouldn't have such a scenario on our hands.
Now, because we've introduced a raw type into the mix, Java can't perform type checking per JLS 4.12.2:
For example, the code:
List l = new ArrayList<Number>();
List<String> ls = l; // Unchecked warning
gives rise to a compile-time unchecked warning, because it is not
possible to ascertain, either at compile time (within the limits of
the compile-time type checking rules) or at run time, whether the
variable l
does indeed refer to a List<String>
.
The situation above is very similar; if we take a look at the first example we used, all we're doing is not adding an extra variable into the matter. The heap pollution occurs all the same.
List rawFooList = new ArrayList();
List<Integer> fooList = rawFooList;
So, while the byte code is identical (likely due to erasure), the fact remains that different or aberrant behavior can arise from a declaration like this.
Don't use raw types, mmkay?