0

How can I reparse java.util.function.Consumer instance and retrieve arguments and values of its lambda expression (forexample "student.person.surname"). Shortly I want to retrieve lambda expression (Consumer) as a text at runtime.

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

}
  • 1
    Using a lambda expression doesn’t imply that Java suddenly ships with a decompiler. Java is not a scripting language. You have an implementation of `Consumer`, nothing more. – Holger Aug 07 '20 at 12:40
  • You mean that "Consumer displayLambda" instance does not contain any lambda expression info without calling accept method, I understand. When need of running lambda function at runtime, at first it handles ConsumerTest.main method which compiled and interpreted and then finds displayLambda line and execute lambda expression. is it true? If so, after compiling and interpreting, there is no way except of using the methods of accessing class content. – zekai üregen Aug 07 '20 at 13:06
  • 1
    No, the source code is converted to bytecode when you run `javac` or your favorite tool’s compiler to create a `.class` file out of the `.java` file. This includes everything in the `.java` file, all classes, methods, and lambda expressions, and happens before you run the compiled code. – Holger Aug 07 '20 at 13:11
  • Thank you for your interest. Is there any way to achive what I want to do? – zekai üregen Aug 07 '20 at 13:22
  • Your comments helps me a lot. However, I have find another way to solve my problem as I posted. Thank you so much. – zekai üregen Aug 10 '20 at 11:34

2 Answers2

0

Thanks a lot for the answers. I have found a way to my problem by using de.danielbechler.diff.ObjectDifferBuilder even though I haven't read lambda expression exactly.

Before running consumer accept method, student object is cloned and after executing displayLambda.accept(student) we can get the difference between changed student object and previous student object. And so we can catch changed parameter and value as seen below.

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

-1

DISCLAIMER: I'm sorry but I couldn't get your question, still I think I could be in favor with adding my toughts. Hopefully not so many "downs" for it :)

It's all about perspective. If I have to explain what a lambda is and how to use it, than I will use the following abstraction:

  1. Functional interface:
  • Single abstract method signature(stucked with public as it's an interface member -> generics, return type, argument types, throws clause -> public <T> void processT(T t), public <T,R> R evalT(T t) etc.)
  • Could have zero or more non abstract methods (default/static).
  • Abstract method has no access to other instance members at any point!
  1. Lambdas:
  • Pure method implementations(or as I call them anonymous method implementations for known functional interfaces). In order for compiler to recognize lambda statement as a valid one it should meet a method singature from any known functional interface at compile time(The target tape could be not annotated with @FunctionalInterface rather than having a single abstract method and being interface itself(ref for abstract classes).

Now, lets take closer look for your particular example:

Consumer -> a void method that accepts one argument(generic, specified type) and does some processing based on the input.

Let's consider your code now, and small showcases that I've added there for you.

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

Now, lets keep digging:

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

    }
}

In the latter example, AnotherTypeOfInterface would have a single method called consumeStudentObject which will match Consumer::accept yet, Consumer instances comes with their own set of members, like Consumer::andThen.

dbl
  • 1,109
  • 9
  • 17
  • I have read your answer completely and also debug. It informed me about the topic. I have find another way to solve my problem as I posted. Thank you so much. – zekai üregen Aug 10 '20 at 11:32