8

在下面的代码中,它在使用类名传递方法引用变量时有效,但是在使用用户对象传递引用变量时出现错误。

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
    }
}
4

6 回答 6

13

userList.forEach期待一个Consumer<? extends User>- 换句话说,一个接受User引用并用它做某事的方法。

那可能是:

  • 接受User参数的静态方法,在这种情况下,参数将在每次迭代时使用列表中的相关元素填充:

    staticMethod(userFromList)
    
  • 一个接受参数的实例方法(任何类的)User,提供了一个特定的实例来调用它 - 再次,参数将填充相关元素:

    target.instanceMethod(userFromList)
    
  • User带参数的实例方法,提供特定实例,在这种情况下,方法调用的目标将是每次迭代时列表中的相关元素:

    userFromList.instanceMethod()
    

因为您尝试指定一个目标并且该方法没有任何参数,所以该forEach方法对每个元素都无能为力 - 它不能将其作为参数传递,因为该方法没有任何参数,并且它不能将其用作方法目标,因为您已经指定了一个。

您的工作代码显示了第三个示例。这里有另外两种方法可以让您演示前两种:

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());
    }
}

然后:

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));

如果你真的想调用printUser相同的User三次,忽略User列表中的,你可以使用:

userList.forEach(ignored -> u1.printName());
于 2014-08-20T16:18:15.560 回答
3

基于http://docs.oracle.com/javase/tutorial/java/javaOO/methodreferences.html我们知道方法引用类似于以下 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)

所以你的代码

userList.forEach(User::printName); // works

可以改写为

userList.forEach((User u) -> u.printName()); // works

这没关系,因为这意味着在这个 lambdas “实现”的accept方法中,你将在每个传递给这个方法的时候调用。ConsumerprintName()User

但万一

userList.forEach(u1::printName); // compile error

此代码表示以下 lambda

userList.forEach((User u) -> u1.printName(u)); // compile error
//                                       ^^^   // method doesn't accept User argument

所以你试图printName从引用持有的实例中调用,并将列表中的u1每个User作为这个方法参数传递,但是正如你所看到的

public void printName() 

不能接受 instance ofUser作为其参数,这就是您看到编译时错误的原因。

于 2014-08-20T16:31:50.503 回答
2

u1::printName

是要在 . 引用的对象上调用的方法引用ui。编译器不知道如何解释它应该传递给Consumerlambda 的参数。它最好的猜测是它应该被传递为

u1.printName(arg);

但是这样的方法不存在。

于 2014-08-20T16:11:10.870 回答
1

方法参考

u1::printName

本质上等同于这个 lambda:

() -> u1.printName()

这是因为printName没有任何论据。如果你有一个printNameWithWidth(int width)方法,那么u1::printNameWithWidth就相当于

(int width) -> u1.printNameWithWidth(width)

但关键是这两种情况都不是User论据之一,因为您已经告诉它User使用哪个(即u1)。 forEach不喜欢那样。它需要一个带有User(或任何其他元素类型)作为参数的 lambda(或等效项)。

这:

User::printName

相当于

(User x) -> x.printName()

这就是它起作用的原因。

于 2014-08-20T16:34:09.720 回答
1
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);

}

即使 B 类的 printName 方法只接受 1 个参数,而 dummyDisplay 方法接受 2 个参数,下面的代码行也能正常工作。这是因为当我们使用 2 个参数调用 dummyDisplay(功能接口)方法时,编译器使用一个参数调用 printName 方法,而另一个参数作为参数传递给 printName 方法。这意味着 (arg1).printName(arg2)。注意方法 printName 中“this”关键字的使用。所以请始终记住,在这种类型的方法引用中,要调用的方法的参数数量(printName)应该始终比方法中使用的参数数量少 1( dummyDisplay)的功能界面。

    MyInterface<B> my1 = B :: printName;
    my1.dummyDisplay(b1,b2);

我希望你理解这个概念。

现在来到下面的代码行。这段代码只是我们刚才讨论的方法引用的替换。在这里,由于在函数式接口中声明的方法有 2 个参数,所以我们必须并且必须在 lambda 表达式中使用 2 个参数(在本例中为 a 和 b)。然后a.printName(b) 将被写为接口方法(dummyDisplay)的定义。它直截了当。Lambda 表达式可以在任何提供功能接口的地方使用(当然)。

    MyInterface<B> my2 = (a,b) -> a.printName(b);
    my2.dummyDisplay(b1,b2);

现在来到最后一段代码。我们得到编译错误,因为编译器期望 B 类的 printName 方法中的参数数量与功能接口方法中的参数数量完全相同。通常这种方法引用仅用于调用任何接受一些参数并对接受的数据进行一些处理的任何类的任何随机方法。例如。比如说计算类中存在的加法/乘法/除法方法或比较器功能接口的比较方法。在所有这些情况下,方法定义不使用“this”关键字。它只是接受一些参数并对它们执行一些任务。我希望你们能从中有所收获。

MyInterface<B> my3 = b1::printName;   //compilation error

说了这么多,现在让我们来回答你的问题,

userList.forEach(User::printName);

工作正常,因为 forEach 方法在内部调用 Consumer 接口的方法 accept(arg1) 并且您的用户定义方法 printName 没有参数。所以按照我上面的解释,它是正确的,编译器不会抱怨。

userList.forEach(u1::printName);

给出编译错误,因为您在引用实例方法 printName 时使用了对象 u1 。因此编译器期望 printName 方法的参数数量与 Consumer 接口的 accept 方法中的参数数量相同。所以它会尝试从你的类 User 中找到 printName(User param1)。而且由于没有找到,编译器的抱怨也差不多。

我希望这对你们有帮助。如果我遗漏了什么或者我说错了什么,也请告诉我。

于 2018-05-24T07:44:04.070 回答
0

我认为这是Java中方法引用使用的关键点。我真的很难学。@Pshemo 的答案对于这种情况来说是非常好的来源。此外,以下图片摘自Java 8 in Action有助于记忆。

在此处输入图像描述

于 2018-07-12T13:32:39.073 回答