在Java方法引用中使用type参数

时间:2018-03-17 05:40:27

标签: java java-8 method-reference

在Java Precisely 3rd Ed。中,有以下代码片段:

BiConsumer<Double[], Comparator<Double>> arraySorter = Arrays::<Double>sort;

但是,我注意到即使我在<Double>之后遗漏::,方法引用仍然有效(由于BiConsumer的类型参数,这是有道理的。)

但是,我对在方法引用中是否存在::<T> 必需的情况感到困惑,如果是这样,一个例子会非常有用。

3 个答案:

答案 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(); 语法)的方法引用,但在两个示例中调用的推理技术是相同。因此,推理的局限性也是一样的。