如何检索 lambda 表达式的参数?

How to retrieve arguments of a lambda expression?

如何重新解析 java.util.function.Consumer 实例并检索其 lambda 表达式的参数和值(例如“student.person.surname”)。不久我想在运行时将 lambda 表达式(消费者)检索为文本。

import lombok.Data;

import java.util.function.Consumer;

public class ConsumerTest {

    @Data
    public static class Person{
        private Integer id;
        private String name;
        private String surName;
    }

    @Data
    public static class Student{
        private Integer id;
        private Person person;
    }

    public static void main(String args[])
    {
        Student student = new Student();
        student.setId(1);
        Person person = new Person();
        person.setName("Ali");
        person.setSurName("Veli");
        person.setId(2);
        student.setPerson(person);

        Consumer<Student> displayLambda = s -> s.getPerson().setSurName("Gulsoy");

        displayLambda.accept(student);

    //I want to reparse displaylambda instance and print arguments. 
    //As here I must be able to retrieve "student.person.surname" and "Gulsoy"
    }

}

免责声明:很抱歉,我无法回答您的问题,但我仍然认为我可以赞成添加我的问题。希望它没有那么多“挫折”:)

一切都与视角有关。如果我必须解释什么是 lambda 以及如何使用它,那么我将使用以下抽象:

  1. 功能接口:
  • 单一抽象方法签名(与 public 卡住,因为它是一个接口成员 -> 泛型,return 类型,参数类型,throws 子句 -> public <T> void processT(T t) public <T,R> R evalT(T t)等)
  • 可以有零个或多个非抽象方法(default/static)。
  • 抽象方法在任何时候都无法访问其他实例成员!
  1. Lambdas:
  • 纯方法实现(或者我称它们为已知功能接口的匿名方法实现)。为了让编译器将 lambda 语句识别为有效语句,它应该在编译时满足来自任何已知功能接口的方法签名(目标磁带可以不使用 @FunctionalInterface 进行注释,而不是具有单个抽象方法并作为接口本身().

现在,让我们仔细看看您的特定示例:

Consumer -> 接受一个参数(泛型,指定类型)并根据输入进行一些处理的 void 方法。

让我们现在考虑您的代码,以及我为您添加的小展示。

import java.util.function.Consumer;

public class ConsumerTest {

    @Data
    public static class Person{
        private Integer id;
        private String name;
        private String surName;
    }

    @Data
    public static class Student{
        private Integer id;
        private Person person;
    }

    public static void main(String args[])
    {
        Student student = new Student();
        student.setId(1);
        Person person = new Person();
        person.setName("Ali");
        person.setSurName("Veli");
        person.setId(2);
        student.setPerson(person);

        /* shorthand definition for anonymous implementation in place, recognisable signature */
        Consumer<Student> displayLambda = s -> s.getPerson().setSurName("Gülsoy");

        /* full definition for anonymous implementation in place, allows annotations */
        Consumer<Student> anotherDisplayLambda = new Consumer<Student>() {

            @Override
            public void accept(Student student) {

                student.getPerson().setSurName("Gülsoy");
            }
        };

        // And finally:
        /* acquires reference to anonymous implementation with recognisable signature */
        Consumer<Student> yetAnotherDisplayLambda = ConsumerTest::thisIsAMethodButAlsoAConsumer;

        /* invokes the implementations, a.k.a. method call, method invocation */
        displayLambda.accept(student);
        anotherDisplayLambda.accept(student);
        yetAnotherDisplayLambda.accept(student);

        /* also valid statements, but this time it captures instance member, so make sure how it works under the hood */
        displayLambda = anotherDisplayLambda::accept; // same as `displayLambda = anotherDisplayLambda`
    }

    // if you can "retrieve that function" here than you should be able to answer your question as well...
    private static void thisIsAMethodButAlsoAConsumer(Student student) {

        student.getPerson().setSurName("Gülsoy");
    }
}

现在,让我们继续挖掘:

import java.util.function.Consumer;

public class ConsumerTest {

    @Data
    public static class Person{
        private Integer id;
        private String name;
        private String surName;
    }

    @Data
    public static class Student{
        private Integer id;
        private Person person;
    }
    private interface AnotherTypeOfInterface /* extends Consumer<Student> */
    {
        // if you can "retrieve that function" here than you should be able to answer your question as well...
        void consumeStudentObject(Student student);
    }
    
    public static void main(String args[])
    {
        Student student = new Student();
        student.setId(1);
        Person person = new Person();
        person.setName("Ali");
        person.setSurName("Veli");
        person.setId(2);
        student.setPerson(person);

        /* Target interface is not annotated as functional, still we got things done :)
         * If you comment out the extend clause in AnotherTypeOfInterface then @FunctionalInterface annotation will be required */
        AnotherTypeOfInterface anotherTypeOfConsumer = ConsumerTest::thisIsAMethodButAlsoAConsumer;

        /* throwsException in thread "main" java.lang.ClassCastException: ConsumerTest$$Lambda/2093631819 cannot be cast to
         * java.util.function.Consumer, unless you comment out the extend clause in interface definition */
//        Consumer<Student> interfacesAreStillTypes = anotherTypeOfConsumer;

        /* but this one doesn't throw as it parses all it needs -> anonymous method signature and definition... */
        Consumer<Student> yetAnotherTypeOfConsumer = anotherTypeOfConsumer::consumeStudentObject

        /* invokes the implementation */
        anotherTypeOfConsumer.consumeStudentObject(student);
//      interfacesAreStillTypes.accept(student);
        yetAnotherTypeOfConsumer.accept(student);

    }
}

在后一个示例中,AnotherTypeOfInterface 将有一个名为 consumeStudentObject 的方法,它将匹配 Consumer::accept 然而,Consumer 实例带有自己的一组成员,例如 Consumer::andThen

非常感谢您的回答。即使我没有完全阅读 lambda 表达式,我也找到了使用 de.danielbechler.diff.ObjectDifferBuilder 解决问题的方法。

在运行 consumer accept 方法之前,克隆了学生对象,在执行displayLambda.accept(student) 之后,我们可以得到更改后的学生对象和之前的学生对象之间的区别。因此我们可以捕获更改的参数和值,如下所示。

import de.danielbechler.diff.ObjectDifferBuilder;
import de.danielbechler.diff.node.DiffNode;
import de.danielbechler.diff.node.Visit;
import lombok.Data;

import java.util.function.Consumer;

public class ConsumerTest {

    @Data
    public static class Person implements Cloneable{
        private Integer id;
        private String name;
        private String surName;

        public Person clone() throws CloneNotSupportedException {
            Person clonedObj = (Person) super.clone();
            clonedObj.name = new String(this.name);
            clonedObj.surName = new String(this.surName);
            clonedObj.id = new Integer(id);
            return clonedObj;
        }
    }

    @Data
    public static class Student implements Cloneable{
        private Integer id;
        private Person person;

        public Student clone() throws CloneNotSupportedException {
            Student clonedObj = (Student) super.clone();
            clonedObj.id = new Integer(id);
            clonedObj.person = this.person.clone();
            return clonedObj;
        }
    }

    public static void main(String args[])
    {
        Consumer<Student> displayLambda = s -> s.getPerson().setSurName("Gülsoy");

        Student student = new Student();
        student.setId(1);
        Person person = new Person();
        person.setName("Ali");
        person.setSurName("Veli");
        person.setId(2);
        student.setPerson(person);

        Student preStudent=null;

        // Before running consumer accept method, clone unchanged student object
        try {
            preStudent = student.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }

        // executing consumer lambda expression with accept method
        displayLambda.accept(student);

        // Checking difference with new Student instance and previous Student instance
        DiffNode diff = ObjectDifferBuilder.buildDefault().compare(preStudent, student);

        if (diff.hasChanges()) {
            Student finalPreStudent = preStudent;
            diff.visit((node, visit) -> {
                if (!node.hasChildren()) { // Only print if the property has no child
                    final Object oldValue = node.canonicalGet(finalPreStudent);
                    final Object newValue = node.canonicalGet(student);

                    // By doing this way we can catch parameter name and changing value
                    final String message = node.getPropertyName() + " changed from " +
                            oldValue + " to " + newValue;
                    System.out.println(message);
                }
            });
        } else {
            System.out.println("No differences");
        }
    }
}