I am trying to use method references with generic classes and have noticed that I can make it work with the equivalent lambda expression, but not with method references. I'm wondering if someone can point out why or tell me what I'm doing wrong...
What I was trying to accomplish when I ran into this issue was to be able to have different builders for different things. The builder receives raw data and a schema containing method references and instructions on when those methods should be called.
I created sandbox code to identify the problem I was having:
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");
}
}
If I create the schemas like this:
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;
}
}
It does not compile:
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
If I change it to use lambda expressions instead, it compiles:
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;
}
}
From what I've read, I thought this was exactly the same as above!?
Method references and explicitly-typed lambda expressions are treated differently.
Let's simplify the example to just:
This code has the same issues as your code with
CarBuilderandArrayList.To determine whether a method reference is compatible with a functional interface type, we first need to figure out the function type to check against. For a method reference, this function type is derived from the target type
Consumer<?>. This turns out to be a function returningvoid, and taking anObjectas parameter. See Function Types for details.Then, in a similar way as overload resolution, we need to resolve the method reference - find a suitable method with the name
fthat is compatible with the function type. Here, the method reference is treated as if it were a method call with an argument of typeObject, i.e.Of course,
JavaClass.ftakes aString, so this does not work, and that's the reason for the error.Explicitly-typed lambda expressions are handled differently. For a functional interface type with wildcards like
Consumer<?>here, Functional Interface Parameterization Inference is used to find the function type to check against. This basically takes into account the parameter types you explicitly stated in the lambda expression. This time, we find the function type to be a function taking aStringand returningvoid. The lambda expression is trivially compatible with that.If the lambda expression had been implicitly-typed,
(s) -> JavaClass.f(s), it would have been treated in a similar way to a method reference.So to use a method reference here, you would need a cast:
See also Type of a Lambda Expression and Type of a Method Reference.
That said, using a
? extendsbound on aConsumerdoesn't make much sense - the consumer wouldn't be able to consume anything exceptnull, without unchecked casts. Remember producersextends, consumerssuper.You should probably stop using raw types too.