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
- You don't need the parentheses surrounding the
p
. For a better readibility, I would suggest to replace(p)
with simply ap
. Hence, your lambda expression will becomep-> p.isHidden()
. - 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
Inserting Text into an Existing File via Java
Where Is the Documentation for the Values() Method of Enum
Converting Secret Key into a String and Vice Versa
Named Placeholders in String Formatting
Java Synchronized Block for .Class
Mockito:How to Verify Method Was Called on an Object Created Within a Method
What Does It Mean: the Serializable Class Does Not Declare a Static Final Serialversionuid Field
Struts 2:There Is No Action Mapped for Namespace [/]
Graphics Rendering in Title Bar
Managing Constructors with Many Parameters in Java
Mockito: Trying to Spy on Method Is Calling the Original Method
Why Do We Assign a Parent Reference to the Child Object in Java
Maven: Failed to Read Artifact Descriptor
Functional Style of Java 8's Optional.Ifpresent and If-Not-Present