在Java Precisely 3rd Ed。中,有以下代码片段:
BiConsumer<Double[], Comparator<Double>> arraySorter = Arrays::<Double>sort;
但是,我注意到即使我在<Double>
之后遗漏::
,方法引用仍然有效(由于BiConsumer
的类型参数,这是有道理的。)
但是,我对在方法引用中是否存在::<T>
必需的情况感到困惑,如果是这样,一个例子会非常有用。
答案 0 :(得分:5)
我认为Java 10的局部变量类型推断(var name = ...;
)将是这个难题的答案。右侧需要完全指定类型,而不是提供方法引用类型的目标变量类型,而需要在方法引用上使用类型参数(::<T>
)。
首先想到了门......
var arraySorter = Arrays::<Double>sort;
...但是方法引用本身并没有定义类型。它们需要由编译器转换为函数对象,并且编译器不会搜索已知的函数接口以查找适当的类型,即使只有一个。
接下来的想法是使用方法引用作为方法的参数,该方法根据方法的参数返回一个类型。
class Spy {
static <T> Function<T,T> f2(Function<T,T> f) {
return f.andThen(f);
}
static <T> T identity(T t) {
return t;
}
}
使用它,我们可以创建我们的局部变量,将方法引用传递给我们的方法:
Function<Double,Double> double_identity = f2(Spy::<Double>identity);
正如所料,我们可以移除::<Double>
Function<Double,Double> double_identity = f2(Spy::identity);
出乎意料的是,局部变量类型推断很好。
var double_identity = f2(Spy::identity); // Infers <Object>!
Object obj = null;
double_identity.apply(obj);
但是当使用方法引用类型覆盖它时真正的惊喜。
var double_identity = f2(Spy::<Double>identity); // Error: Double != Object
经过一番战斗,我弄明白了为什么。我们必须将类型应用于f2
方法本身:
var double_identity = Spy.<Double>f2(Spy::identity); // Works.
回想起来,这是有道理的。变量的类型通常为外部函数提供上下文。将结果分配给Function<Double,Double>
变量可让编译器推断出f2(...)
的类型,然后将该类型传递给参数。对于var name = ...
,没有显式类型,唯一可用的类型是Object
,因此编译器推断Spy.<Object>f2(...)
,然后确定参数类型必须是Function<Object,Object>
。
不幸的是,它似乎没有从内到外解析,因此Spy::<Double>identity
不会导致函数被推断为Spy.<Double>f2(...)
而变量被推断为{{1} }}。也许Java 11?也许它会破坏太多,并且无法工作。
Function<Double,Double>
来解决OP的难题。
非常感谢@Eugene批评我之前在Java 10发布之前的尝试。
答案 1 :(得分:4)
这是类型推断正在运行中,在大多数情况下,编译器会为您推断出类型,因此意味着您无需明确提供它们。
但是,在某些情况下,您需要手动提供类型提示。
无论如何,Java 8增强了泛型参数的推断。
所以,执行以下操作:
BiConsumer<Double[], Comparator<Double>> arraySorter = Arrays::sort;
由于类型推断而完全有效。
我现在可以想到的一些例子在Java-7中不起作用但在Java-8中起作用就像:
void exampleMethod(List<Person> people) {
// do logic
}
exampleMethod(Collections.emptyList())
另一个例子:
someMethodName(new HashMap<>());
...
void someMethodName(Map<String, String> values);
您需要先前明确提供类型参数。
此外,由于前面提到的类型推断,我们现在可以做以下事情的确切原因是:
...
...
.collect(Collectors.toList());
而不是这个人:
...
...
.collect(Collectors.<Person>toList());
在某些情况下,是否应该明确提供类型参数是一个优先事项,在其他情况下,为了帮助编译器完成其工作,您必须这样做。
答案 2 :(得分:1)
Java 8实现了Generalized Target-Type Inference (JEP 101),它允许编译器推断泛型方法的类型参数。在您的示例中,Java 8编译器推断出方法的类型参数{{1从作业的右侧开始。
JEP 101也为链式方法调用提出了广义目标类型推断,但是由于它将引入推理算法的复杂性而没有实现(讨论here和{{3} })。因此,链式泛型方法调用是一个无法推断泛型方法的类型参数的示例。
请考虑以下代码段:
sort
编译器无法推断class Main {
String s = MyList.nil().head(); // Incompatible types. Required: String. Found: Object.
static class MyList<E> {
private E head;
static <Z> MyList<Z> nil() { return new MyList(); }
E head() { return head; }
}
}
中泛型方法nil()
的类型参数。因此,我们必须通过添加类型参数
String s = MyList.nil().head()
或拆分链接的电话
String s = MyList.<String>nil().head();
注意:链式调用示例不像原始问题那样包含对泛型方法(MyList<String> ls = MyList.nil();
String s = ls.head();
语法)的方法引用,但在两个示例中调用的推理技术是相同。因此,推理的局限性也是一样的。