当运行时类型也是泛型类型时,将方法引用用作期望与泛型类型接口的方法的 lambda 时出现编译器错误
Compiler errors when using method reference as lambda to methods that expect interface with generic type, when runtime type is also generic type
我正在尝试编写一些代码来接收 java.util.function.*
包中 classes 的各种实现,但我保留 运行 以防止特定的编译器错误我真的很想解决的语法(像 obj.<Type>foo(/*...*/)
这样的显式类型参数有效,但它们缺乏优雅)。
简而言之,我希望能够始终尽可能使用方法引用,但出于某种原因,我不明白为什么编译器无法理解下面解释的引用。
假设以下两个classes(实现无关):
某些实体 class 具有 getter/setter 方法:
class Thing {
public List<String> getThings() {
return null;
}
public void setThings(List<String> things) {
// ...
}
}
一些其他 class 对该实体的实例执行操作 class:
class FooBar<A> {
public <B> void foo(Function<A, List<B>> f) {
// ...
}
public <B> void bar(BiConsumer<A, List<B>> f) {
// ...
}
public <B> void wuz(Function<A, List<B>> x, BiConsumer<A, List<B>> y) {
// ...
}
}
当执行对这些方法的调用时,编译器给我各种错误:
// create an instance of the handler class
FooBar<Thing> fb = new FooBar<>();
例如。 1) 调用期望函数的方法工作正常,没有编译错误:
fb.foo(Thing::getThings);
例如。 2) 然而调用期望双消费者的方法给我这个:
fb.bar(Thing::setThings);
// The type Thing does not define setThings(Thing, List<B>) that is applicable here
例如。 3) 正如预期的那样,明确说明类型工作得很好,没有编译错误:
fb.<String>bar(Thing::setThings);
例如。 4) 然而,当我写出 lambda 时,它给了我一个不同的编译错误(尽管在 lambda 参数中说明类型工作正常):
fb.bar((thing, things) -> thing.setThings(things));
// The method setThings(List<String>) in the type Thing is not applicable for the arguments (List<Object>)
例如。 5) 当调用需要两者的方法时,每个参数都有不同的编译错误:
fb.wuz(
Thing::getThings,
// The type of getThings() from the type Thing is List<String>, this is incompatible with the descriptor's return type: List<B>
Thing::setThings
// The type Thing does not define setThings(Thing, List<B>) that is applicable here
);
例如。 6) 同样,正如预期的那样,明确声明类型再次正常工作,没有编译错误:
fb.<String>wuz(Thing::getThings, Thing::setThings);
例如。 7) 这就是最让我迷惑的地方:对于需要函数和双消费者的方法,写出 just 双消费者(无类型)并使用函数的方法引用,工作正常,没有编译错误(?!):
fb.wuz(Thing::getThings, (thing, things) -> thing.setThings(things));
我不明白的是,显然,编译器在确定不同场景下的运行时 type/type 擦除时会以不同方式处理显式 lambda 和方法引用,即:
- 获取参数和returns值的功能接口
- 获取参数但returns没有值的功能接口
- 带有 1. 和 2. 类型参数的方法
这似乎只发生在函数接口的类型本身是泛型类型(在本例中为 List)时,这让我相信这与类型擦除有关,但我在不知所措。
我希望能够写...
fb.wuz(Thing::getThings, Thing::setThings);
...没有显式类型或普通 lambda 表达式。
如果有一种方法可以重构 FooBar
中的方法来支持这一点,我真的很想知道。
当然,如果有人能够解释编译器的这些不同行为,我也将不胜感激! :-)
编辑
我特别想知道为什么示例 #1 和 #7 可以 工作,但为什么示例 #4 本身 不能 有效(然后,为什么示例 #2 也不起作用)。
提出的问题实际上似乎不是编译错误,而是 Eclipse 编辑器中的错误。出现错误的 Eclipse 版本是 Luna EE 4.4.0。更新到 Luna EE 4.4.2 后,编辑器的行为符合我的预期(以及 Java 编译器的行为方式)。
我正在尝试编写一些代码来接收 java.util.function.*
包中 classes 的各种实现,但我保留 运行 以防止特定的编译器错误我真的很想解决的语法(像 obj.<Type>foo(/*...*/)
这样的显式类型参数有效,但它们缺乏优雅)。
简而言之,我希望能够始终尽可能使用方法引用,但出于某种原因,我不明白为什么编译器无法理解下面解释的引用。
假设以下两个classes(实现无关):
某些实体 class 具有 getter/setter 方法:
class Thing {
public List<String> getThings() {
return null;
}
public void setThings(List<String> things) {
// ...
}
}
一些其他 class 对该实体的实例执行操作 class:
class FooBar<A> {
public <B> void foo(Function<A, List<B>> f) {
// ...
}
public <B> void bar(BiConsumer<A, List<B>> f) {
// ...
}
public <B> void wuz(Function<A, List<B>> x, BiConsumer<A, List<B>> y) {
// ...
}
}
当执行对这些方法的调用时,编译器给我各种错误:
// create an instance of the handler class
FooBar<Thing> fb = new FooBar<>();
例如。 1) 调用期望函数的方法工作正常,没有编译错误:
fb.foo(Thing::getThings);
例如。 2) 然而调用期望双消费者的方法给我这个:
fb.bar(Thing::setThings);
// The type Thing does not define setThings(Thing, List<B>) that is applicable here
例如。 3) 正如预期的那样,明确说明类型工作得很好,没有编译错误:
fb.<String>bar(Thing::setThings);
例如。 4) 然而,当我写出 lambda 时,它给了我一个不同的编译错误(尽管在 lambda 参数中说明类型工作正常):
fb.bar((thing, things) -> thing.setThings(things));
// The method setThings(List<String>) in the type Thing is not applicable for the arguments (List<Object>)
例如。 5) 当调用需要两者的方法时,每个参数都有不同的编译错误:
fb.wuz(
Thing::getThings,
// The type of getThings() from the type Thing is List<String>, this is incompatible with the descriptor's return type: List<B>
Thing::setThings
// The type Thing does not define setThings(Thing, List<B>) that is applicable here
);
例如。 6) 同样,正如预期的那样,明确声明类型再次正常工作,没有编译错误:
fb.<String>wuz(Thing::getThings, Thing::setThings);
例如。 7) 这就是最让我迷惑的地方:对于需要函数和双消费者的方法,写出 just 双消费者(无类型)并使用函数的方法引用,工作正常,没有编译错误(?!):
fb.wuz(Thing::getThings, (thing, things) -> thing.setThings(things));
我不明白的是,显然,编译器在确定不同场景下的运行时 type/type 擦除时会以不同方式处理显式 lambda 和方法引用,即:
- 获取参数和returns值的功能接口
- 获取参数但returns没有值的功能接口
- 带有 1. 和 2. 类型参数的方法
这似乎只发生在函数接口的类型本身是泛型类型(在本例中为 List)时,这让我相信这与类型擦除有关,但我在不知所措。
我希望能够写...
fb.wuz(Thing::getThings, Thing::setThings);
...没有显式类型或普通 lambda 表达式。
如果有一种方法可以重构 FooBar
中的方法来支持这一点,我真的很想知道。
当然,如果有人能够解释编译器的这些不同行为,我也将不胜感激! :-)
编辑
我特别想知道为什么示例 #1 和 #7 可以 工作,但为什么示例 #4 本身 不能 有效(然后,为什么示例 #2 也不起作用)。
提出的问题实际上似乎不是编译错误,而是 Eclipse 编辑器中的错误。出现错误的 Eclipse 版本是 Luna EE 4.4.0。更新到 Luna EE 4.4.2 后,编辑器的行为符合我的预期(以及 Java 编译器的行为方式)。