引用特定对象的实例方法

时间:2014-08-20 16:08:36

标签: java lambda java-8 method-reference

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

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

6 个答案:

答案 0 :(得分:13)

userList.forEach期待Consumer<? extends User> - 换句话说,这是一种接受User引用并对其执行操作的方法。

那可能是:

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

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

    target.instanceMethod(userFromList)
    
  • User上带 no 参数的实例方法,没有特定实例, target 方法调用将是每次迭代时列表中的相关元素:

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

答案 1 :(得分: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

这是正常的,因为这意味着在accept这个lambdas“实现”的Consumer方法中,您将在传递给此方法的每个printName()上调用User。< / p>

但是在

的情况下
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() 

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

答案 2 :(得分:2)

u1::printName

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

传递
u1.printName(arg);

但这种方法不存在。

答案 3 :(得分: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()

这就是它起作用的原因。

答案 4 :(得分: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)功能界面。在处理POJO类时经常使用这种方法引用,其中我们使用给定功能接口的类的getter(no arg)(比如Function / Consumer - 具有带1个参数的方法)。

    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方法中具有完全相同数量的参数。通常,这种方法引用仅用于调用任何接受某些参数的类的随机方法,并对接受的数据进行一些处理。例如。比如在Complate类功能​​接口的Calculate或compare方法中出现的add / multiply / divide方法。在所有这些情况下,方法定义不使用“this”关键字。它只接受一些参数并对它们执行一些任务。我希望你们能从中得到一些东西。

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

话虽如此,现在让我们回答你的问题,

userList.forEach(User::printName);

工作正常,因为forEach方法在内部调用Consumer接口的方法accept(arg1),而用户定义的方法printName没有args。因此,根据我的上述解释,它是正确的,编译器不会抱怨。

userList.forEach(u1::printName);

给出编译错误,因为在引用实例方法printName时使用对象u1。因此编译器需要与Consumer接口的accept方法相同的printName方法参数。因此它会尝试从您的类User中找到printName(User param1)。并且由于没有找到,编译器投诉也是如此。

我希望这可以帮助你们。如果我遗漏了任何内容,或者我说错了,也请告诉我。

答案 5 :(得分:0)

我认为这是在Java中使用方法引用的关键点。我真的很难学习它。 @Pshemo's answer对于这种情况确实是很好的来源。此外,以下Java 8 in Action中摘录的图片有助于记住。

enter image description here