问题
In the following code, it works when passing the method reference variable with the class name, but when passing the reference variable with a user object there is an error.
public class User {
private String name;
public User(String name) {
this.name = name;
}
public void printName() {
System.out.println(name);
}
}
public class Main {
public static void main(String[] args) {
User u1 = new User("AAA");
User u2 = new User("BBB");
User u3 = new User("ZZZ");
List<User> userList = Arrays.asList(u1, u2, u3);
userList.forEach(User::printName); // works
userList.forEach(u1::printName); // compile error
}
}
回答1:
userList.forEach
is expecting a Consumer<? extends User>
- in other words, a method which accept a User
reference and do something with it.
That could be:
A static method accepting a
User
parameter, in which case the parameter will be populated with the relevant element in the list on each iteration:staticMethod(userFromList)
An instance method (of any class) accepting a
User
parameter, provided with a specific instance to call it on - again, the parameter will be populated with the relevant element:target.instanceMethod(userFromList)
An instance method on
User
with no parameters, provided without a specific instance, which case the target of the method call will be the relevant element in the list on each iteration:userFromList.instanceMethod()
Because you've tried to specify a target and the method doesn't have any parameters, the forEach
method has nothing it can do with each element - it can't pass it as an argument because the method doesn't have any parameters, and it can't use it as the method target because you've already specified one.
Your working code shows the third example. Here are two other methods to allow you to demonstrate the first two:
public class UserPrinter {
private final String name;
public UserPrinter(String name) {
this.name;
}
public static void staticPrintUser(User user) {
// Assuming you add a User.getName() method
System.out.println("staticPrintUser: " + user.getName());
}
public void instancePrintUser(User user) {
System.out.println("instancePrintUser (instance " + name + "): "
+ user.getName());
}
}
Then:
userList.forEach(UserPrinter::staticPrintUser); // equivalent to
//userList.forEach(p -> UserPrinter.staticPrintUser(p));
UserPrinter printer = new UserPrinter("my printer");
userList.forEach(printer::instancePrintUser); // equivalent to
//userList.forEach(p -> printer.instancePrintUser(p));
If you really want to call printUser
on the same User
three times, ignoring the User
in the list, you could use:
userList.forEach(ignored -> u1.printName());
回答2:
Based on http://docs.oracle.com/javase/tutorial/java/javaOO/methodreferences.html we know that method references are similar to following lambdas
method reference ==> lambda
------------------------------------------------------------------------------------
object::method ==> (Foo f, Bar b, Baz z) -> object.method(f,b,z)
SomeClass::staticMethod ==> (Foo f, Bar b, Baz z) -> SomeClass.staticMethod(f,b,z)
SomeClass::instanceMethod ==> (Foo f, Bar b, Baz z) -> f.instanceMethod(b,z)
SomeClass::new ==> (Foo f, Bar b, Baz z) -> new SomeClass(f,b,z)
So your code
userList.forEach(User::printName); // works
can be rewritten as
userList.forEach((User u) -> u.printName()); // works
which is OK because it means that in accept
method of Consumer
which this lambdas "implements" you will invoke printName()
on each User
passed to this method.
But in case of
userList.forEach(u1::printName); // compile error
this code represents following lambda
userList.forEach((User u) -> u1.printName(u)); // compile error
// ^^^ // method doesn't accept User argument
so you are trying to invoke printName
from instance held by u1
reference and pass each User
from list as this method argument, but as you see
public void printName()
can't accept instance of User
as its argument, which is why you are seeing compile time error.
回答3:
This
u1::printName
is a method reference to be invoked on the object referenced by ui
. The compiler doesn't know how to interpret the argument it should pass to the Consumer
lambda. Its best guess is that it should be passed as
u1.printName(arg);
but such a method doesn't exist.
回答4:
The method reference
u1::printName
is essentially equivalent to this lambda:
() -> u1.printName()
This is because printName
doesn't have any arguments. If you had a printNameWithWidth(int width)
method, then u1::printNameWithWidth
would be equivalent to
(int width) -> u1.printNameWithWidth(width)
But the point is that in neither case is a User
one of the arguments, since you've already told it which User
to use (i.e. u1
). forEach
doesn't like that. It needs a lambda (or the equivalent) with a User
(or whatever other element type) as an argument.
This:
User::printName
is equivalent to
(User x) -> x.printName()
which is why it works.
回答5:
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.function.BiPredicate;
import java.util.function.Function;
import java.util.function.Predicate;
public class Testing {
public static void main(String[] args) {
List<B> list = new ArrayList<B>();
B b1=new B(); b1.setName("Durgesh");
B b2=new B(); b2.setName("Val");
list.add(b1);list.add(b2);
MyInterface<B> my1 = B :: printName;
my1.dummyDisplay(b1,b2);
MyInterface<B> my2 = (a,b) -> a.printName(b);
my2.dummyDisplay(b1,b2);
// MyInterface<B> my3 = b1::printName; //compilation error
}
}
class B{
String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void printName(B b) {
System.out.println(this.name + b.name);
}
}
@FunctionalInterface
interface MyInterface <T> {
public void dummyDisplay(T s, T t);
}
Below lines of code works fine even though printName method of class B accepts only 1 parameter while method dummyDisplay accepts 2 parameters. It is because when we call dummyDisplay ( of functional interface ) method with 2 arguments, compiler uses one argument to invoke printName method and another argument is passed as an argument to printName method. That means (arg1).printName(arg2). Notice the usage of "this" keyword in the method printName.So always remember that in such kinds of method references, number of paramters of the method to be called(printName) should always be 1 less than the number of parameters used in method(dummyDisplay) of functional interface. Such kind of method reference is very frequently used when dealing with POJO classes where we use getters(no arg) of the classes given the functional interface(say Function/Consumer - having methods with 1 parameter).
MyInterface<B> my1 = B :: printName;
my1.dummyDisplay(b1,b2);
I hope you understand this concept.
Now coming to below lines of code. This code is just a replacement of method reference which we discussed just now. Here, since method declared in functional interface has 2 parameters, so we have to and had to use 2 arguments in lambda expression (in this case its a and b). Then a.printName(b) will be written as the definition of interface method (dummyDisplay). Its straight forward. Lambda expression can be used any where provided Functional interfaces(off course).
MyInterface<B> my2 = (a,b) -> a.printName(b);
my2.dummyDisplay(b1,b2);
Now coming to last piece of code. We get compilation error because compiler expects exactly the same number of parameters in printName method of class B which are there in the method of functional interface. Normally this kind of method reference is used just to call any random method of any class which accepts some parameters and does some processing on the accepted data. eg. say add/multiply/divide methods present in the class Calculate or the compare method of Comparator functional interface. In all these cases, method definition does not use "this" keyword. It simply accepts some parameters and performs some task on them. I hope you guys got something out of it.
MyInterface<B> my3 = b1::printName; //compilation error
Having said this, Now lets come to your question,
userList.forEach(User::printName);
works fine because, forEach method internally calls a method accept(arg1) of Consumer interface and your user-defined method printName has no args. So as per my above explanation, it is correct and the compiler does not complaint.
and
userList.forEach(u1::printName);
gives compilation error, because you are using object u1 while referencing instance method printName . So compiler expects same number of parameters of printName method as that there are in accept method of Consumer interface. So it will try to find printName(User param1) from your class User. And since it is not found, compiler complaints about the same.
I hope this helps you guys. Also let me know if I have missed anything or if I have said some thing wrong.
回答6:
I think it's the key point of usage of method references in Java. I had really difficulty in learning it. @Pshemo's answer is really nice source for this case. Besides, the following image excerpted from Java 8 in Action is helpful to remember.
来源:https://stackoverflow.com/questions/25409533/reference-to-an-instance-method-of-a-particular-object