有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

对特定对象的实例方法的java引用

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

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) 个答案

  1. # 1 楼答案

    基于http://docs.oracle.com/javase/tutorial/java/javaOO/methodreferences.html我们知道方法引用类似于下面的lambda

    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方法中,您将在传递给这个方法的每个User上调用printName()

    但万一

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

    此代码表示以下lambda

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

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

    public void printName() 
    

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

  2. # 2 楼答案

    我认为这是在Java中使用方法引用的关键点。我真的很难学@Pshemo's answer是这个案子的好消息来源。此外,以下摘自Java 8 in Action的图片有助于记忆

    enter image description here

  3. # 3 楼答案

    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个参数。这是因为当我们用两个参数调用dummyDisplay(函数接口的)方法时,编译器使用一个参数调用printName方法,另一个参数作为参数传递给printName方法。这意味着(arg1)。打印名(arg2)。注意printName方法中使用的“this”关键字。所以请记住,在此类方法引用中,要调用的方法(printName)的参数数应始终比函数接口的方法(dummyDisplay)中使用的参数数少1。在处理POJO类时,我们经常使用这种方法引用,在POJO类中,我们使用给定函数接口的类的getter(无arg)(比如Function/Consumer,具有带1个参数的方法)

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

    我希望你理解这个概念

    下面是代码行。这段代码只是我们刚才讨论的方法参考的替代品。在这里,因为函数接口中声明的方法有两个参数,所以我们必须在lambda表达式中使用两个参数(在本例中是a和b)。然后a.printName(b)将被编写为接口方法(dummyDisplay)的定义。这是直截了当的。Lambda表达式可以在提供功能接口的任何地方使用(非正常)

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

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

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

    说到这里,现在让我们来回答你的问题

    userList.forEach(User::printName);
    

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

    userList.forEach(u1::printName);
    

    给出编译错误,因为您在引用实例方法printName时使用的是对象u1。所以编译器期望printName方法的参数数量和使用者接口的accept方法中的参数数量相同。因此,它将尝试从类用户中查找printName(用户param1)。由于没有找到,编译器也会抱怨同样的问题

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

  4. # 4 楼答案

    方法参考

    u1::printName
    

    本质上相当于这个λ:

    () -> u1.printName()
    

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

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

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

    这是:

    User::printName
    

    相当于

    (User x) -> x.printName()
    

    这就是它起作用的原因

  5. # 5 楼答案

    这个

    u1::printName
    

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

    u1.printName(arg);
    

    但这种方法并不存在

  6. # 6 楼答案

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

    如果真的想在同一个User上调用printUser三次,忽略列表中的User,可以使用:

    userList.forEach(ignored -> u1.printName());