Instance Method Reference and Lambda Parameters

Instance Method Reference and Lambda Parameters

I think you're looking for JLS section 15.13.3, which includes:

If the form is ReferenceType :: [TypeArguments] Identifier, the body of the invocation method similarly has the effect of a method invocation expression for a compile-time declaration which is the compile-time declaration of the method reference expression. Run-time evaluation of the method invocation expression is as specified in §15.12.4.3, §15.12.4.4, and §15.12.4.5, where:

  • The invocation mode is derived from the compile-time declaration as specified in §15.12.3.

  • If the compile-time declaration is an instance method, then the target reference is the first formal parameter of the invocation method. Otherwise, there is no target reference.

  • If the compile-time declaration is an instance method, then the arguments to the method invocation expression (if any) are the second and subsequent formal parameters of the invocation method. Otherwise, the arguments to the method invocation expression are the formal parameters of the invocation method.

Note the last two bullets, basically.

For example, what if there are three or more parameters to the lambda? Is that legal? Does the first parameter become the method target, and the remaining become the parameters?

Yup :)

Method reference to instance method using instance type

The reason why you can use ObjectType::instanceMethod seemingly without an instance is because that method reference represents a method that accepts ObjectType as a parameter, followed by parameters of the instanceMethod.

class A {
public void f(String s) {}
}

Here, You can think of A::f as representing a "static" method like this:

public static void f(A a, String s) {}

So in your example, you need a BiConsumer to represent a method accepting two parameters and returning nothing:

BiConsumer<DummyConsumer, String> c = DummyConsumer::consume;

In the Shipment case, Shipment::calculateWeight represents a "static" method like this:

public static double calculateWeight(Shipment s) { ... }

This is why Shipment::calculateWeight can be represented by Function<Shipment, Double>.

So in general, you just need to first figure out what kind of method does a method reference represent, and find a functional interface that represents that kind of method. Sometimes, you need to create a functional interface yourself!

How Java treat method reference of instance methods?

String::compareToIgnoreCase conforms to Comparator<String> because it does accept two String arguments and returns an int. The first argument is the instance of String upon which to invoke the instance method. The second argument is the (single) parameter to be passed to that instance method.

It's the same as this

(a, b) -> a.compareToIgnoreCase(b)

or more verbosely

(String a, String b) -> { 
return a.compareToIgnoreCase(b);
}

So yes, compareToIgnoreCase only takes one argument, but String::compareToIgnoreCase takes two.

Contrast that with "foo"::compareToIgnoreCase (or say, someSpecificStr::compareToIgnoreCase). Here, the instance is already bound, so it only takes one argument.

How is method reference syntax working in Java 8

What is a method refernce

You can see a method reference as a lambda expression, that call an existing method.

Kinds of method references

There are four kinds of method references:

  • Reference to a static method: ContainingClass::staticMethodName
  • Reference to an instance method of a particular object: containingObject::instanceMethodName
  • Reference to an instance method of an arbitrary object of a particular type: ContainingType::methodName
  • Reference to a constructor: ClassName::new

How to understand it?

The following expression that lists all hidden files:
f.listFiles(p -> p.isHidden());

This expression is composed by the instance method listFiles(FileFilter) and your lambda expression p -> p.isHidden() that is not a anonymous method but an existing instance method of the class File.

Note that FileFilter is a functional interface and can therefore be used as the assignment target for a lambda expression or method reference. Hence, you can write your expression f.listFiles(File::isHidden);

Side notes

  1. You don't need the parentheses surrounding the p. For a better readibility, I would suggest to replace (p) with simply a p. Hence, your lambda expression will become p-> p.isHidden().
  2. Your for loop can be replaced by an enhanced for loop:
for (File value : hidden) {
System.out.println(value);
}

Documentation:

Method reference

FileFilter

Using method reference instead of multi argument lambda

SomeClass::func can mean two things, depending on whether func is a static method or an instance method.

(1) If func is a static method, then SomeClass::func is a lambda that just passes all the arguments to the method:

(a, b, c) -> SomeClass.func(a, b, c);

(2) If func is an instance method, then SomeClass::func is a lambda that uses the first argument as the instance, as you thought:

(a, b, c) -> a.func(b, c);

where a has type SomeClass.

EDIT: Sotirios' answer demonstrates yet a different type of method reference: example::method where example is a reference variable (instead of a class name). This means the same as

(a, b) -> example.method(a, b);

or perhaps more accurately

(a, b) -> __someFinalTemporary.method(a, b);

where __someFinalTemporary is assigned to example at the point where the method reference is evaluated, so that if example changes later, the method is still called using the earlier value of example.

[The fourth kind is SomeClass::new which passes the arguments to a constructor. I think that's all of them.]

Method reference - Difference between Reference to a static method and Reference to an instance method of an arbitrary object of a particular type

Explanation

The method toUpperCase is not a static method... so why can one write in the way above, rather than needing to use it this way:

weeks.stream().map(s -> s.toUpperCase()).forEach(System.out::println);

Method references are not limited to static methods. Take a look at

.map(String::toUpperCase)

it is equivalent to

.map(s -> s.toUpperCase())

Java will just call the method you have referenced on the elements in the stream. In fact, this is the whole point of references.

The official Oracle tutorial explains this in more detail.



Insights, Examples

The method Stream#map (documentation) has the following signature:

<R> Stream<R> map​(Function<? super T, ? extends R> mapper)

So it expects some Function. In your case this is a Function<String, String> which takes a String, applies some method on it and then returns a String.

Now we take a look at Function (documentation). It has the following method:

R apply​(T t)

Applies this function to the given argument.

This is exactly what you are providing with your method reference. You provide a Function<String, String> that applies the given method reference on all objects. Your apply would look like:

String apply(String t) {
return t.toUpperCase();
}

And the Lambda expression

.map(s -> s.toUpperCase())

generates the exact same Function with the same apply method.

So what you could do is

Function<String, String> toUpper1 = String::toUpperCase;
Function<String, String> toUpper2 = s -> s.toUpperCase();

System.out.println(toUpper1.apply("test"));
System.out.println(toUpper2.apply("test"));

And they will both output "TEST", they behave the same.

More details on this can be found in the Java Language Specification JLS§15.13. Especially take a look at the examples in the end of the chapter.

Another note, why does Java even know that String::toUpperCase should be interpreted as Function<String, String>? Well, in general it does not. That's why we always need to clearly specify the type:

// The left side of the statement makes it clear to the compiler
Function<String, String> toUpper1 = String::toUpperCase;

// The signature of the 'map' method makes it clear to the compiler
.map(String::toUpperCase)

Also note that we can only do such stuff with functional interfaces:

@FunctionalInterface
public interface Function<T, R> { ... }


Note on System.out::println

For some reason you are not confused by

.forEach(System.out::println);

This method is not static either.

The out is an ordinary object instance and the println is a non static method of the PrintStream (documentation) class. See System#out for the objects documentation.



Related Topics



Leave a reply



Submit