Java 方法参考中类型参数的使用
Use of type parameter in Java method reference
在 Java 恰好是第 3 版中,有以下代码片段:
BiConsumer<Double[], Comparator<Double>> arraySorter = Arrays::<Double>sort;
但是,我注意到即使我在 ::
之后省略 <Double>
,方法引用仍然有效(由于 BiConsumer
的类型参数,这是有道理的)。
但是,我很困惑在方法引用中是否存在 ::<T>
是 必要的 的情况,如果是这样,一个例子是很有帮助。
Java 8 实现 Generalized Target-Type Inference (JEP 101) 允许编译器推断泛型方法的 type 参数。 在您的示例中,Java 8 编译器从赋值的右侧推断方法的类型参数sort
。
JEP 101 also proposed generalized target-type inference for chained method calls, but it was not implemented because of the complexity that it would have introduced to the inference algorithm (discussed here and here)。因此,chained generic-method calls 是无法推断泛型方法的类型参数的示例。
考虑以下代码片段:
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; }
}
}
编译器无法为 String s = MyList.nil().head()
中的泛型方法 nil()
推断类型参数。因此,我们必须为推理算法提供更多信息,或者通过添加类型参数
String s = MyList.<String>nil().head();
或拆分链式调用
MyList<String> ls = MyList.nil();
String s = ls.head();
注意: chained-calls 示例不包含原始问题中对泛型方法(::<>
语法)的方法引用,但是两个示例中调用的推理技术是相同的。所以,推理的局限性也是一样的。
这是类型推断的实际应用,在大多数情况下,编译器会为您推断类型,因此您无需明确提供它们。
但是,在某些情况下您需要手动提供类型提示。
无论如何,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());
是否应该显式提供类型参数在某些情况下是偏好问题,而在其他情况下,您被迫这样做是为了帮助编译器完成工作。
我想 Java 10 的局部变量类型推断 (var name = ...;
) 将是这个难题的答案。 right-hand-side 需要完全指定类型,而不是为方法引用提供类型的目标变量类型,需要在方法引用上使用类型参数 (::<T>
)。
先想到门...
var arraySorter = Arrays::<Double>sort;
...但是方法引用本身并没有定义类型。它们需要由编译器转换为功能对象,编译器不会搜索已知的功能接口来寻找合适的类型,即使恰好有一个。
接下来的想法是使用方法引用作为方法的参数,returns 一个基于方法参数的类型。
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(...)
而变量被推断为Function<Double,Double>
.也许 Java 11?也许它会破坏太多,无法工作。
然而,它确实结束了我滥用 var name = ...;
来解决 OP 难题的尝试。
非常感谢@Eugene 在 Java 10 发布之前批评我之前的尝试。
在 Java 恰好是第 3 版中,有以下代码片段:
BiConsumer<Double[], Comparator<Double>> arraySorter = Arrays::<Double>sort;
但是,我注意到即使我在 ::
之后省略 <Double>
,方法引用仍然有效(由于 BiConsumer
的类型参数,这是有道理的)。
但是,我很困惑在方法引用中是否存在 ::<T>
是 必要的 的情况,如果是这样,一个例子是很有帮助。
Java 8 实现 Generalized Target-Type Inference (JEP 101) 允许编译器推断泛型方法的 type 参数。 在您的示例中,Java 8 编译器从赋值的右侧推断方法的类型参数sort
。
JEP 101 also proposed generalized target-type inference for chained method calls, but it was not implemented because of the complexity that it would have introduced to the inference algorithm (discussed here and here)。因此,chained generic-method calls 是无法推断泛型方法的类型参数的示例。
考虑以下代码片段:
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; }
}
}
编译器无法为 String s = MyList.nil().head()
中的泛型方法 nil()
推断类型参数。因此,我们必须为推理算法提供更多信息,或者通过添加类型参数
String s = MyList.<String>nil().head();
或拆分链式调用
MyList<String> ls = MyList.nil();
String s = ls.head();
注意: chained-calls 示例不包含原始问题中对泛型方法(::<>
语法)的方法引用,但是两个示例中调用的推理技术是相同的。所以,推理的局限性也是一样的。
这是类型推断的实际应用,在大多数情况下,编译器会为您推断类型,因此您无需明确提供它们。
但是,在某些情况下您需要手动提供类型提示。
无论如何,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());
是否应该显式提供类型参数在某些情况下是偏好问题,而在其他情况下,您被迫这样做是为了帮助编译器完成工作。
我想 Java 10 的局部变量类型推断 (var name = ...;
) 将是这个难题的答案。 right-hand-side 需要完全指定类型,而不是为方法引用提供类型的目标变量类型,需要在方法引用上使用类型参数 (::<T>
)。
先想到门...
var arraySorter = Arrays::<Double>sort;
...但是方法引用本身并没有定义类型。它们需要由编译器转换为功能对象,编译器不会搜索已知的功能接口来寻找合适的类型,即使恰好有一个。
接下来的想法是使用方法引用作为方法的参数,returns 一个基于方法参数的类型。
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(...)
而变量被推断为Function<Double,Double>
.也许 Java 11?也许它会破坏太多,无法工作。
然而,它确实结束了我滥用 var name = ...;
来解决 OP 难题的尝试。
非常感谢@Eugene 在 Java 10 发布之前批评我之前的尝试。