Почему компилятор не может определить правильный тип результата из Collections.emptySet () в следующем примере?
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());
}
});
}
}
Вот сообщение об ошибке компилятора:
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
Теперь, конечно, работает следующая реализация getColl ():
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());
}
});
}
и вся цель методов безопасного типа в коллекциях состоит в том, чтобы избежать такого рода проблем с одноэлементными коллекциями (в отличие от использования статических переменных). Итак, компилятор просто не может выполнять логический вывод на нескольких уровнях универсальных шаблонов? Что происходит?




Для вывода Java требуется много усилий. Во многих случаях система типов могла бы сделать вывод лучше, но в вашем случае будет работать следующее:
print("Collections.<String>emptySet();");
Замечательно, я раньше не видел такого синтаксиса. Спасибо!
Просто используйте GenericFactory, который был представлен Блохом во 2-м издании Effective Java ...
Похоже на проблему приведения типов, то есть на то, что требуется преобразовать Object (в Set<Object>, который был бы типом пустого набора) в String. Принятие решений в общем случае небезопасно.
Collections.emptySet() не является Collection<String>, если Java не знает, что ему там нужен Collection<String>. В этом случае кажется, что компилятор несколько глупо говорит о порядке, в котором он пытается определить типы, и пытается определить тип возвращаемого значения Collections.emptySet(), прежде чем пытаться определить предполагаемый тип параметра шаблона для B на самом деле String.
Решение состоит в том, чтобы прямо заявить, что вам нужен Collections.<String>emptySet(), как упоминал GaryF.
Сначала вы можете сузить проблему до этого кода:
public class Test {
public void option(Collection<String> b) {
}
public void getColl() {
option(Collections.emptySet());
}
}
Это не работает, вам нужна временная переменная, иначе компилятор не сможет определить тип. Вот хорошее объяснение этой проблемы: Почему временные переменные имеют значение в случае вызова общих методов?
Хорошая статья. В частности, ключевой момент, который отвечает на исходный вопрос, выглядит следующим образом: «вызов метода не учитывается для вывода типа».
Пример, в котором Java могла бы работать лучше и рассматривается для Java 7, выглядит примерно так: Map <String, Integer> model = new HashMap <> ();