views:

518

answers:

4

Why is the compiler unable to infer the correct type for the result from Collections.emptySet() in the following example?

import java.util.*;
import java.io.*;

public class Test {
    public interface Option<A> {
        public <B> B option(B b, F<A,B> f);
    }

    public interface F<A,B> {
        public B f(A a);
    }

    public Collection<String> getColl() {
        Option<Integer> iopt = null;

        return iopt.option(Collections.emptySet(), new F<Integer, Collection<String>>() {
            public Collection<String> f(Integer i) {
                return Collections.singleton(i.toString());
            }
        });
    }
}

Here's the compiler error message:

knuttycombe@knuttycombe-ubuntu:~/tmp/java$ javac Test.java 
Test.java:16: <B>option(B,Test.F<java.lang.Integer,B>) in 
Test.Option<java.lang.Integer> cannot be applied to (java.util.Set<java.lang.Object>,
<anonymous Test.F<java.lang.Integer,java.util.Collection<java.lang.String>>>)
            return iopt.option(Collections.emptySet(), new F<Integer, Collection<String>>() {
                   ^
1 error

Now, the following implementation of getColl() works, of course:

    public Collection<String> getColl() {
        Option<Integer> iopt = null;

        Collection<String> empty = Collections.emptySet();
        return iopt.option(empty, new F<Integer, Collection<String>>() {
            public Collection<String> f(Integer i) {
                return Collections.singleton(i.toString());
            }
        });
    }

and the whole intent of the typesafe methods on Collections is to avoid this sort of issue with the singleton collections (as opposed to using the static variables.) So is the compiler simply unable to perform inference across multiple levels of generics? What's going on?

+7  A: 

Java needs a lot of hand holding with its inference. The type system could infer better in a lot of cases but in your case the following will work:

print("Collections.<String>emptySet();");
GaryF
An example where Java could do better, and is being considered for Java 7 is something like: Map<String,Integer> model = new HashMap<>();
GaryF
That's great, I had not see that syntax before. Thank you!
Kris Nuttycombe
Just use the GenericFactory that was introduced by Bloch in the 2nd Edition of Effective Java...
André
A: 

It looks like a typecasting issue - i.e., that it's being required to cast Object (in Set<Object>, which would be the type of the empty set) to String. Downcasts are not, in the general case, safe.

bradheintz
+1  A: 

Collections.emptySet() is not a Collection<String> unless Java knows that it needs a Collection<String> there. In this case, it appears that the compiler is being somewhat silly about the order that it tries to determine types, and tries to determine the return type of Collections.emptySet() before trying to determine the intended template parameter type for B is actually String.

The solution is to explictly state that you need Collections.<String>emptySet(), as mentioned by GaryF.

Yuliy
+4  A: 

First you can narrow down your problem to this code:

public class Test { 
    public void option(Collection<String> b) {
    }

    public void getColl() {
     option(Collections.emptySet());
    }
}

This does not work, you need a temporary variable or else the compiler cannot infer the type. Here is a good explanation of this problem: Why do temporary variables matter in case of invocation of generic methods?

martinus
Nice article. Very specifically, the key point that answers the original question appears to be: "method invocation is not considered for type inference."
Dave Costa