Why is it legal to pass a method reference of one parameter as an argument of expected type BiConsumer
whose abstract method requires two arguments?
Example:
class Experiment {
private String name;
public Experiment(String name) {
this.name = name;
}
public void oneParamMethod(Object o) {
System.out.println(this.name + " and " + o);
}
public <T, S> void executeBiConsumer(BiConsumer<T, S> biCon, T in1, S in2) {
biCon.accept(in1, in2);
}
public static void main(String[] args) {
// notice that the name is "INSTANCE", but it won't be printed out
Experiment exp = new Experiment("INSTANCE");
// executeBiConsumer expects a functional of two params but is given a method
// reference of one param. HOW IS THIS LEGAL?
exp.executeBiConsumer(Experiment::oneParamMethod, new Experiment("PARAM"), 999);
}
}
Output:
PARAM and 999
Let's change the invocation such that the second argument is not an instance of Experiment
as follows:
exp.executeBiConsumer(Experiment::oneParamMethod, new String("INVALID"), 999);
Now, it won't compile.
Experiment
instance, and why does it not compile otherwise?BiConsumer
?A method reference referencing an instance method having one argument actually has two arguments - the first argument is implicit - the instance on which the method is executed.
Experiment::oneParamMethod
is equivalent to (Experiment e, Object o) -> e.oneParamMethod(o)
.
The BiConsumer<T, S>
you are passing to executeBiConsumer
is a BiConsumer<Experiment,Object>
, which means it must receive an instance of Experiment
as the first argument of the accept
method.
Therefore
exp.executeBiConsumer(Experiment::oneParamMethod, new Experiment("PARAM"), 999);
is valid, but
exp.executeBiConsumer(Experiment::oneParamMethod, new String("INVALID"), 999);
is not.
Here's a relevant JLS reference (15.13.1):
Second, given a targeted function type with n parameters, a set of potentially applicable methods is identified:
If the method reference expression has the form ReferenceType :: [TypeArguments] Identifier, the potentially applicable methods are the member methods of the type to search that have an appropriate name (given by Identifier), accessibility, arity (n or n-1), and type argument arity (derived from [TypeArguments]), as specified in §15.12.2.1.
Two different arities, n and n-1, are considered, to account for the possibility that this form refers to either a static method or an instance method.
Your targeted function type - BiConsumer
- has 2 parameters. Therefore the potentially applicable methods are the member methods of the type to search (Experiment
) that have the appropriate name (oneParamMethod
) and arity 2 or 1 (i.e. 1 or 2 arguments). This includes your public void oneParamMethod(Object o)
method.
Adding upon Eran's answer
There are four kinds of method references
The one you are using belongs to the third category. We can see this better if we substitute the method reference using lambda.
What you are doing is
BiConsumer<Experiment, Integer> biCon = (experiment, someInt) ->
experiment.oneParamMethod(someInt)
The first argument becomes the object
on which the oneParamMethod
is called. The method reference equivalent of the above is what you were using - Experiment::oneParamMethod
.
If you were to convert oneParamMethod
static, you would get an error as Class::staticMethod
's lambda form would be to pass the arguments as it is to the static method.
It would look like
BiConsumer<Experiment, Integer> biCon = (experiment, someInt) ->
Experiment.oneParamMethod(experiment, someInt)
and you don't have a oneParamMethod
method taking two parameters.
References:
Oracle Method reference
Method References
Ah, I have a problem with the description of the third kind.
This is not as complex as it sounds. We use Class::instanceMethod
most of the time when we use stream..filter..map
. Let us say, we want to filter Person
object whose age is more than 18.
persons.stream()
.filter(person -> person.getAge() > 18)
.map(person -> person.getName()) //Get only name
...
Here, person -> person.getName()
can be written as Person::getName
. This is the same case as 3rd category. The first implict argument is the arbitrary object of a particular type and getName
is the instance method.
Hope this helps
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With