简体   繁体   中英

Why can't the Java compiler figure this out?

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?

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();");

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?

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.

It looks like a typecasting issue - ie, 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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM