Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does giving explicit type arguments to a non-generic method or constructor compile?

When instantiating ArrayLists I am used to seeing code like this

ArrayList<Type> arr = new ArrayList<Type>();

or

ArrayList<Type> arr = new ArrayList<>();

however today I have come across an instantiation of ArrayList that looks like this:

ArrayList<Type> arr = new <Type>ArrayList();

what is going on, and why does that give an "unsafe operations" compile warning?

like image 317
Will Sherwood Avatar asked Mar 04 '14 14:03

Will Sherwood


People also ask

Why parameterized type is important in Java?

Like generics in C# and Java, parameterized types allow you to create “containers” that can hold other types. For example, List<String> represents a List containing only strings, and KeyValuePair<int, string> represents a pair of values in which an int serves as a key to a string .

What is the purpose of using generics?

In a nutshell, generics enable types (classes and interfaces) to be parameters when defining classes, interfaces and methods. Much like the more familiar formal parameters used in method declarations, type parameters provide a way for you to re-use the same code with different inputs.

Why are generics important in Java?

Generics ensure compile-time safety which allows the programmer to catch the invalid types while compiling the code. Java Generics helps the programmer to reuse the code for whatever type he/she wishes. For instance, a programmer writes a generic method for sorting an array of objects.

Which types can be used as arguments of a generic type?

The actual type arguments of a generic type are. reference types, wildcards, or. parameterized types (i.e. instantiations of other generic types).


3 Answers

Edit:

Yes, found the reference. See JLS §15.12.2.1 - Identify Potentially Applicable Methods:

If the method invocation includes explicit type arguments, and the member is a generic method, then the number of type arguments is equal to the number of type parameters of the method.

  • This clause implies that a non-generic method may be potentially applicable to an invocation that supplies explicit type arguments. Indeed, it may turn out to be applicable. In such a case, the type arguments will simply be ignored.

Emphasis mine.

Also see JLS §15.9.3 - Choosing the Constructor and its Arguments, for understanding how the constructor invocation is resolved. It also mentions that the above mentioned process is followed for resolution.


Original answer:

Such kind of invocation is often required, when you have a generic constructor, and the compiler is not able to infer the correct type arguments. For example, consider the below code:

class Demo<T> {
    public <X> Demo(X[] arg1, X arg2) { 
        // initialization code
        System.out.println(arg1.getClass());
        System.out.println(arg2.getClass());
    }
}

Suppose you invoke that constructor like this:

Demo<String> demo = new Demo<String>(new String[2], new Integer(5));

You would think that the type inference should fail, as the type arguments should have same types. Here we're passing String and Integer types. But it doesn't. The compiler infers the type X as:

Object & Serializable & Comparable<? extends Object&Serializable&Comparable<?>>

Now, you might want the type parameter to be inferred as just Object, then in that case, you can provide explicit type arguments, as in the below code:

Demo<String> demo = new <Object>Demo<String>(new String[2], new Integer(5));

This is similar to how you give explicit type argument while method invocation.

Now, in your code, you have given the explicit type arguments, but you're using raw type of the class to instantiate it:

ArrayList<Integer> arr = new <String>ArrayList();

The <String> is the explicit type argument for the constructor, and compiler will be fine with it. But the issue is, you're instantiating raw type ArrayList, and that is where compiler is giving your unchecked warning. If you change that code to:

ArrayList<Integer> arr = new <String>ArrayList<>();

The warning will go away. But since ArrayList constructor is not a generic constructor, the type argument seems to be just ignored by the constructor. In fact there is no use of that type argument there.

Strangely enough, this also compiles:

public static void test() { }

public static void main(String... args) {
    Main.<Integer>test();
}

...even though test() is a non-generic method.

like image 138
Rohit Jain Avatar answered Oct 04 '22 05:10

Rohit Jain


I've just tried:

ArrayList<Integer> arr = new <String>ArrayList();

And got the same warning (not an error!). Looks like the compiler ignores1 the generics after the new keyword and before the ArrayList. It's just like writing:

ArrayList<Integer> arr = new ArrayList();

1 I'm not sure if it really "ignores" that, I'll be glad if someone confirms/correct me

like image 40
Maroun Avatar answered Oct 04 '22 06:10

Maroun


The code, it does nothing!

int a = new <String>Integer(5);

It also compiles but generates a warning of "unused generics".

So basically it is useless but not bad enough to generate an error by default it seems. Either way your arraylist is NOT properly generified here.

Please note that generics are compiled away anyway so at the bytecode level it probably won't look any different. I have looked in eclipse for a way to turn this into an error instead of warning but no luck there.

UPDATE

This answer boils down to the same as the other one which is currently at +5, so why is mine downvoted? Please leave a comment if you downvote.

like image 43
nablex Avatar answered Oct 04 '22 05:10

nablex