26

I have some code that I would write

GenericClass<Foo> foos = new GenericClass<>();

While a colleague would write it

GenericClass<Foo> foos = new GenericClass();

arguing that in this case the diamond operator adds nothing.

I'm aware that constructors that actually use arguments related to the generic type can cause a compile time error with <> instead of a run time error in the raw case. And that the compile time error is much better. (As outlined in this question)

I'm also quite aware that the compiler (and IDE) can generate warnings for the assignment of raw types to generics.

The question is instead for the case where there are no arguments, or no arguments related to the generic type. In that case, is there any way the constructed object GenericClass<Foo> foos can differ depending on which constructor was used, or does Javas type erasure guarantee they are identical?

Community
  • 1
  • 1
Michael Anderson
  • 70,661
  • 7
  • 134
  • 187
  • Also possibly duplicated: http://stackoverflow.com/questions/22203257/is-this-raw-type-assignment-type-safe-listt-new-arraylist – Radiodef Mar 09 '15 at 03:12
  • 1
    Not a duplicate of either. I know what the diamond operator is. I'm asking can the two methods actually generate a difference in the resulting object in the zero-argument case. I'm yet to see a single example of that. (Though often there are demonstrations of how a compilation error becomes a RTE for a single argument constructor). – Michael Anderson Mar 09 '15 at 03:27
  • 2
    Just to be clear: do you believe that, if the constructed instances are identical, then your colleague is correct that "the diamond operator adds nothing"? Because the constructed instances *are* identical, and even so your colleague is quite wrong. If that seems contradictory to you, then you didn't ask the right question. :-) – ruakh Mar 09 '15 at 03:55
  • @ruakh Even if they are guaranteed to be identical I would still prefer the `<>` version - but that could be argued to be personal preference. However, if there is some way that the non-`<>` version could differ in behaviour to the `<>` version I would have better grounds to convince my colleague to also prefer them. – Michael Anderson Mar 09 '15 at 03:59
  • 5
    In your question, you already indicate a way that the two versions can differ in behavior (namely that, when the constructor takes arguments whose types refer to the type parameter, that the compiler can carry its type-checking all the way through); and the current answer indicates another (namely that the compiler will give a warning for a raw type). If your colleague thinks "I'm perfect, and everyone who ever touches this code will be perfect, and I don't want the compiler to help us type-check it", then I think (s)he's moved beyond the realm of "personal preference". – ruakh Mar 09 '15 at 04:10
  • There is a nice article about this on [DZone](https://dzone.com/articles/java-7-do-we-really-need). – R. Oosterholt Nov 16 '15 at 15:04

5 Answers5

11

For instantiations of two ArrayLists, 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 Integers 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?

Community
  • 1
  • 1
Makoto
  • 104,088
  • 27
  • 192
  • 230
  • I suspected as much for `ArrayList`, but is it true in general? Is there something about java's type-erasure that guarantees it to be true. – Michael Anderson Mar 09 '15 at 03:54
  • In general I'd expect this to be the case; `ArrayList` is one example of a class that uses a constructor not related to its type argument. As for it being a "guarantee" - I'd be careful about that. I don't see anything in the JLS that explicitly guarantees this to be true, so it's entirely possible that this is a happy side-effect from the generic type declaration. [4.5.2 comes close](http://docs.oracle.com/javase/specs/jls/se7/html/jls-4.html#jls-4.5.2), but it doesn't quite satisfy me in terms of a bonafide guarantee. – Makoto Mar 09 '15 at 04:05
  • @Makoto: [§8.1.2](http://docs.oracle.com/javase/specs/jls/se8/html/jls-8.html#jls-8.1.2) is probably more directly relevant: although "a generic class declaration defines a set of parameterized types", it specifies that "all of these parameterized types share the same class at run time". (In other words: erasure happens.) Since [§12.5](http://docs.oracle.com/javase/specs/jls/se7/html/jls-12.html#jls-12.5) specifies class instance creation solely in terms of the *class*, not the *type*, this means that class instance creation is subject to erasure. – ruakh Mar 09 '15 at 04:18
  • 1. I don't consider the Echo example is valid due to the presence of the `Class` parameter, which is an _argument related to the generic type._ – Michael Anderson Jul 12 '15 at 07:49
  • 2. as far as I can tell the `List rawList = new ArrayList(); List list = rawList;` example is only a problem due to the reference to `rawList` still existing. Which is clearly not the case for my example. – Michael Anderson Jul 12 '15 at 07:52
3

The JLS is actually pretty clear on this point. http://docs.oracle.com/javase/specs/jls/se8/html/jls-8.html#jls-8.1.2

First it says "A generic class declaration defines a set of parameterized types (§4.5), one for each possible parameterization of the type parameter section by type arguments. All of these parameterized types share the same class at run time."

Then it gives us the code block

Vector<String>  x = new Vector<String>();
Vector<Integer> y = new Vector<Integer>();
boolean b = x.getClass() == y.getClass();

and says that it "will result in the variable b holding the value true."

The test for instance equality (==) says that both x and y share exactly the same Class object.

Now do it with the diamond operator and without.

Vector<Integer> z = new Vector<>();
Vector<Integer> w = new Vector();
boolean c = z.getClass() == w.getClass();
boolean d = y.getClass() == z.getClass();

Again, c is true, and so is d.

So if, as I understand, you're asking whether there is some difference at runtime or in the bytecode between using the diamond and not, the answer is simple. There is no difference.

Whether it's better to use the diamond operator in this case is a matter of style and opinion.

P.S. Don't shoot the messenger. I would always use the diamond operator in this case. But that's just because I like what the compiler does for me in general w/r/t generics, and I don't want to fall into any bad habits.

P.P.S. Don't forget that this may be a temporary phenomenon. http://docs.oracle.com/javase/specs/jls/se8/html/jls-4.html#jls-4.8 warns us that "The use of raw types in code written after the introduction of generics into the Java programming language is strongly discouraged. It is possible that future versions of the Java programming language will disallow the use of raw types."

Erick G. Hagstrom
  • 4,873
  • 1
  • 24
  • 38
  • 1
    I think that the combination of the constructors being the same (since they're the same class at run time - as this answer points out), and there being no way to access the `Class` within the constructor (i.e. its not provided as an implicit parameter), means that there's no way that the two cases can differ. – Michael Anderson Jul 13 '15 at 00:28
  • 1
    It's my opinion, but that non-raw types future will unlikely to come. Looks like this notice was added in JDK 1.5 along with generic types. Java continues to support almost all the deprecated methods since Java 1.0, even really bad things like `Thread.suspend()`. There's so much code involving raw-types (even written much after Java 1.5), so removing their support would be a compatibility disaster. – Tagir Valeev Jul 13 '15 at 08:13
1

You may have problem with default constructor if your generic arguments are limited. For example, here's sloppy and incomplete implementation of the list of numbers which tracks the total sum:

public class NumberList<T extends Number> extends AbstractList<T> {
    List<T> list = new ArrayList<>();
    double sum = 0;

    @Override
    public void add(int index, T element) {
        list.add(index, element);
        sum += element.doubleValue();
    }

    @Override
    public T remove(int index) {
        T removed = list.remove(index);
        sum -= removed.doubleValue();
        return removed;
    }

    @Override
    public T get(int index) {
        return list.get(index);
    }

    @Override
    public int size() {
        return list.size();
    }

    public double getSum() {
        return sum;
    }
}

Omitting the generic arguments for default constructor may lead to ClassCastException in runtime:

List<String> list = new NumberList(); // compiles with warning and runs normally
list.add("test"); // throws CCE

However adding the diamond operator will produce a compile-time error:

List<String> list = new NumberList<>(); // error: incompatible types
list.add("test");
Tagir Valeev
  • 97,161
  • 19
  • 222
  • 334
  • 1
    I agree that casting to a generic interface is a good argument for using the `<>` operator. However, once the object is constructed in a non -constrained setting, either via `NumberList n = new NumberList<>()` or `NumberList n = new NumberList()` this doesn't really apply. – Michael Anderson Jul 08 '15 at 07:12
  • This adds a compile-time error because `List list = new NumberList<>` is equivalent to saying `List list = new NumberList`, which fails because `String` isn't in the bound of `Number`. This means that it's checked *at compile time*, and Java's generic checking system is working as it should be. If you play fast and loose with bounded generics, what you're stating is actually what should be expected; a `ClassCastException` since you're telling Java that you know what you're doing by omitting the type checking. – Makoto Jul 09 '15 at 02:26
1

In your specific example: Yes, they are identical.

Generally: Beware, they may not be!

The reason is that different overloaded constructor/method may be invoked when raw type is used; it is not only that you get better type safety and avoid runtime ClassCastException.

Overloaded constructors

public class Main {

    public static void main(String[] args) {
        Integer anInteger = Integer.valueOf(1);
        GenericClass<Integer> foosRaw = new GenericClass(anInteger);
        GenericClass<Integer> foosDiamond = new GenericClass<>(anInteger);
    }

    private static class GenericClass<T> {

        public GenericClass(Number number) {
            System.out.println("Number");
        }

        public GenericClass(T t) {
            System.out.println("Parameter");
        }
    }
}

Version with diamond invokes the different constructor; the output of the above program is:

Number
Parameter

Overloaded methods

public class Main {

    public static void main(String[] args) {
        method(new GenericClass());
        method(new GenericClass<>());
    }

    private static void method(GenericClass<Integer> genericClass) {
        System.out.println("First method");
    }

    private static void method(Object object) {
        System.out.println("Second method");
    }

    private static class GenericClass<T> { }
}

Version with diamond invokes the different method; the output:

First method
Second method
Dragan Bozanovic
  • 23,102
  • 5
  • 43
  • 110
  • In this case the constructor takes an argument related to `T`. Yet the question is pretty explicit about requiring all arguments be unrelated to `T`. However the potential confusion due to overloaded constructors is an interesting additional potential pitfall. – Michael Anderson Jul 14 '15 at 03:51
  • @MichaelAnderson I added an example of method overloading as well. In this case the constructor is parameterless, but the point is the same: there may be situations in which the execution path of the program is different if diamond is left out. – Dragan Bozanovic Jul 14 '15 at 06:39
0

This is not a complete answer - but does provide a few more details.

While you can not distinguish calls like

GenericClass<T> x1 = new GenericClass<>();
GenericClass<T> x2 = new GenericClass<T>();
GenericClass<T> x3 = new GenericClass();

There are tools that will allow you to distinguish between

GenericClass<T> x4 = new GenericClass<T>() { };
GenericClass<T> x5 = new GenericClass() { };

Note: While it looks like we're missing new GenericClass<>() { }, it is not currently valid Java.

The key being that type information about the generic parameters are stored for anonymous classes. In particular we can get to the generic parameters via

Type superclass = x.getClass().getGenericSuperclass();
Type tType = (superclass instanceof ParameterizedType) ?
             ((ParameterizedType) superclass).getActualTypeArguments()[0] : 
             null;
  • For x1, x2, and x3 tType will be an instance of TypeVariableImpl (the same instance in all three cases, which is not surprising as getClass() returns the same object for all three cases.

  • For x4 tType will be T.class

  • For x5 getGenericSuperclass() does not return an instance of ParameterizedType, but instead a Class (infact GenericClass.class)

We could then use this to determine whether our obect was constructed via (x1,x2 or x3) or x4 or x5.

Michael Anderson
  • 70,661
  • 7
  • 134
  • 187