Andrew Mao
Andrew Mao

Reputation: 36940

Uses for the strange-looking explicit type argument declaration syntax in Java

I recently came upon the strange syntax for explicitly declaring generic types when calling Java methods. For example:

Collections.<String>emptyList();

returns an empty List<String>. However, this seems silly as the implementation of <T> emptyList() is just the unchecked type cast (List<T>) EMPTY_LIST, such that all results have the same type erasure (and are the same object.) Moreover, this sort of explicit type declaration is usually not needed because the compiler can often infer the types:

List<String> empty = Collections.emptyList();

After doing some more digging I found two other times where you'd want to use this syntax, and they're all due to using the Guava library and apparently trying to put too many statements on one line.

  1. Decorating a collection, for example with a synchronized wrapper, and the compiler being not able to infer the types. The following doesn't work if you take out the type declaration: cannot convert from Set<Object> to Set<String>:

    Set<String> set = Collections.synchronizedSet(Sets.<String>newHashSet());
    
  2. Getting less specific type parameters when they compiler tries to make ones that are too specific. For example, without the type declaration the following statement complains as well: cannot convert from Map<String, String> to Map<String, Object>:

    Map<String, Object> toJson = ImmutableMap.<String, Object>of("foo", "bar");
    

I find it ironic that in the first case the inferred type parameters are too general and in the second case they are too specific, but I suppose that is just an artifact of the generics system in Java.

However, this language construct itself seems to be avoidable except in these strange use cases invented by the Guava team. Moreover, it seems plain to me that there is a way for the compiler to infer type arguments in both the above examples, and the developers just chose not to do so. Are there examples of it ever being necessary or useful to use this construct in Java programming or does it exist solely to make the compiler simpler / JDK developer's life easier?

Upvotes: 14

Views: 4636

Answers (3)

matiash
matiash

Reputation: 55380

I found at least one case where the compiler infers the types correctly, and it's still needed: when you want to use the result as a more generic type. Take this method, which basically creates a List<T> from zero or more T objects:

public static <T> List<T> listOf(T... items) {
    ArrayList<T> list = new ArrayList<T>();
    for (T item : items)
        list.add(item);

    return list;
}

The idea is that you can use it like this:

List<Integer> numbers = ListUtils.listOf(1, 2, 3);

Now, suppose you have a method that can receive List<Object>:

public static void a(List<Object> objs) {
    ...
}

and that you want to supply a list built via the listOf() method:

a(ListUtils.listOf(1, 2, 3));

This will not compile, as the method parameter type is List<Object> and the supplied argument is List<Integer>. In that case, we can change the invocation to:

a(ListUtils.<Object>listOf(1, 2, 3));

which does compile, as expected.

Upvotes: 1

Matt Ball
Matt Ball

Reputation: 360046

How is "shutting up the compiler" not "necessary or useful?" I find it both necessary and useful for my code to compile.

There are times when the correct type cannot be inferred, as you have already found. In such cases, it is necessary to explicitly specify the type parameters. Some examples of the compiler just not being smart enough:

And if you really want to dig into the complexities of type inference, it starts and ends with the Java Language Specification. You'll want to focus on JLS §15.12.2.7. Inferring Type Arguments Based on Actual Arguments and §15.12.2.8. Inferring Unresolved Type Arguments.

Upvotes: 5

Judge Mental
Judge Mental

Reputation: 5239

Java type inference is incredibly weak. The only time it is not necessary to include the explicit type in a generic method like emptyList() is when the result of the method defines a variable. If you try to pass an empty list as the argument of another method (example 1), a situation which arises for me on a daily basis (and I do not yet use Guava), the compiler just gives up on type inference completely. I fail to see how declaring the empty list as a local, single-use variable is "putting too many statements on one line" as you call it; the empty list is a very simple sub-expression, except that Java's miserable type inference makes it complex. Compare with Scala, which will do inference in 3 different situations.

Upvotes: 0

Related Questions