Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

BiConsumer and method reference of one parameter [duplicate]

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.


  1. Why does the code compile without complain if the second argument is an Experiment instance, and why does it not compile otherwise?
  2. Why is it valid to pass a method reference of only one parameter as an argument that expects BiConsumer?
like image 708
Niko Gambt Avatar asked Dec 10 '22 02:12

Niko Gambt


2 Answers

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.

like image 176
Eran Avatar answered Dec 21 '22 23:12

Eran


Adding upon Eran's answer

There are four kinds of method references

  1. Reference to a static method
  2. Reference to an instance method of a particular object
  3. Reference to an instance method of an arbitrary object of a particular type
  4. Reference to a constructor

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

like image 41
user7 Avatar answered Dec 22 '22 00:12

user7