【问题标题】:How to retrieve arguments of a lambda expression?如何检索 lambda 表达式的参数?
【发布时间】:2020-08-07 11:24:31
【问题描述】:

如何重新解析 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 表达式并不意味着 Java 突然带有反编译器。 Java 不是脚本语言。你有一个Consumer&lt;Student&gt; 的实现,仅此而已。
  • 您的意思是“Consumer displayLambda”实例不包含任何 lambda 表达式信息而不调用 accept 方法,我理解。当需要在运行时运行 lambda 函数时,它首先处理编译和解释的 ConsumerTest.main 方法,然后找到 displayLambda 行并执行 lambda 表达式。是真的吗?如果是这样,编译和解释后,除了使用访问类内容的方法之外别无他法。
  • 不,当您运行javac 或您喜欢的工具的编译器以从.java 文件中创建.class 文件时,源代码将转换为字节码。这包括 .java 文件中的所有内容、所有类、方法和 lambda 表达式,并且发生在您运行编译后的代码之前。
  • 感谢您的关注。有什么方法可以实现我想做的事情吗?
  • 您的 cmets 对我帮助很大。但是,正如我发布的那样,我找到了另一种解决问题的方法。非常感谢。

标签: java lambda reflection java-8 consumer


【解决方案1】:

免责声明:很抱歉,我无法回答您的问题,但我仍然认为我可以加入我的观点。希望不会有那么多“挫折”:)

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

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

现在,让我们仔细看看您的具体示例:

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$3/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

【讨论】:

  • 我已完整阅读您的答案并进行了调试。它告诉了我这个话题。正如我发布的那样,我找到了另一种解决问题的方法。非常感谢。
【解决方案2】:

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

在运行消费者接受方法之前,学生对象被克隆,在执行 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");
        }
    }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-11-02
    • 1970-01-01
    相关资源
    最近更新 更多