我正在尝试将方法引用与泛型类一起使用,并且注意到我可以使其与等效的 lambda 表达式一起使用,但不能与方法引用一起使用。我想知道是否有人可以指出原因或告诉我我做错了什么......
当我遇到这个问题时,我试图实现的目标是能够让不同的构建器处理不同的事情。构建器接收原始数据和包含方法引用的模式以及有关何时调用这些方法的说明。
我创建了沙箱代码来识别我遇到的问题:
public class Builder<T extends List> {
public void doA(T list) {
System.out.println("doA");
}
}
public class CarBuilder extends Builder<ArrayList> {
public void doB(ArrayList list) {
System.out.println("doB");
}
}
如果我创建这样的模式:
public class Schema {
BiConsumer<? extends Builder, ? extends List> methodReference;
public static Schema builderSchema = new Schema(Builder::doA);
public static Schema carBuilderSchema = new Schema(CarBuilder::doB);
private Schema(BiConsumer<? extends Builder, ? extends List> methodReference) {
this.methodReference = methodReference;
}
}
它无法编译:
Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.1:compile (default-compile) on project Sandbox: Compilation failure
com/sandbox/Schema.java:[11,56] incompatible types: invalid method reference
method doB in class com.sandbox.CarBuilder cannot be applied to given types
required: java.util.ArrayList
found: com.sandbox.Builder,java.util.List
reason: actual and formal argument lists differ in length
如果我将其更改为使用 lambda 表达式,它会编译:
public class Schema {
BiConsumer<? extends Builder, ? extends List> methodReference;
public static Schema builderSchema = new Schema((Builder builder, List list) -> builder.doA(list));
public static Schema carBuilderSchema = new Schema((CarBuilder builder, ArrayList list) -> builder.doB(list));
private Schema(BiConsumer<? extends Builder, ? extends List> methodReference) {
this.methodReference = methodReference;
}
}
从我读到的内容来看,我认为这和上面一模一样!?
方法引用和显式类型的 lambda 表达式的处理方式不同。
让我们将示例简化为:
public class JavaClass {
public static void main(String[] args) {
Consumer<? /* extends Object*/> a = JavaClass::f; // error
a = (String s) -> JavaClass.f(s); // works
Consumer<String> b = JavaClass::f;
a = b; // also works
}
static void f(String s) {}
}
此代码与
CarBuilder
和 ArrayList
的代码具有相同的问题。
要确定方法引用是否与函数式接口类型兼容,我们首先需要找出要检查的函数类型。对于方法引用,此函数类型派生自目标类型
Consumer<?>
。事实证明,这是一个返回 void
并以 Object
作为参数的函数。有关详细信息,请参阅函数类型。
然后,以与重载解析类似的方式,我们需要解决方法引用 - 找到一个名称为
f
且与函数类型兼容的合适方法。在这里,方法引用被视为带有 Object
类型参数的方法调用,即
void temp(Object o) {
// if overload resolution successfully finds an overload for this call,
// that overload is what the method reference is referencing
JavaClass.f(o);
}
当然,
JavaClass.f
需要一个String
,所以这不起作用,这就是错误的原因。
显式类型的 lambda 表达式的处理方式有所不同。对于带有通配符的函数式接口类型(如此处的
Consumer<?>
),函数式接口参数化推理用于查找要检查的函数类型。这基本上考虑了您在 lambda 表达式中明确指定的参数类型。这次,我们发现函数类型是一个接受 String
并返回 void
的函数。 lambda 表达式与此基本兼容。
如果 lambda 表达式是隐式类型的,
(s) -> JavaClass.f(s)
,它将以与方法引用类似的方式处理。
因此,要在此处使用方法引用,您需要进行强制转换:
Consumer<?> a = (Consumer<String>)JavaClass::f;
new Schema((BiConsumer<CarBuilder, ArrayList>)CarBuilder::doB);
另请参阅Lambda 表达式的类型 和方法引用的类型。
也就是说,在
? extends
上使用 Consumer
绑定没有多大意义 - 如果没有未经检查的强制转换,消费者将无法使用除 null
之外的任何内容。记住生产者extends
,消费者super
。
您也应该停止使用原始类型。