Endovelicvs
Endovelicvs

Reputation: 119

Invoking methods in Comparator comparing method

I have an ArrayList<Task> named tasks which I want to print, sorted according to each field of the Task object. Task class has three private fields { String title, Date date, String project }. The class has some public get methods that allow other classes to read the fields { getTitle(), getDate(), getProject(), getTaskDetails() }.

I have a simple method that uses a stream to sort and print the ArryaList tasks:

tasks.stream()
     .sorted(Comparator.comparing(Task::getProject))
     .map(Task::getTaskDetails)
     .forEach(System.out::println);

Instead of creating 3 different methods, to sort according each different getter method, I wanted to use Reflection API. But I am having trouble invoking the methods ( getTitle(), getDate(), getProject() ) inside the Comparator comparing method:

Used import java.lang.reflect.Method;

Declared the method Method taskMethod = Task.class.getDeclaredMethod(methodName); where methodName will be the parameter String received with the method name ("getTitle" or "getDate" or "getProject").

Then tried to do something like this, but didn't workout:

            tasks.stream()
                    .sorted(Comparator.comparing(task -> {
                                try {
                                    taskMethod.invoke(task);
                                } catch (Exception e) {
                                    e.printStackTrace();
                                }
                            }))
                    .map(Task::getTaskDetails)
                    .forEach(System.out::println);

Is this possible at all? Or is there any simpler solution?

Only found this question but didn't solve my problem.

Thank you for any feedback.

Upvotes: 0

Views: 1618

Answers (2)

Ezequiel
Ezequiel

Reputation: 3592

Using reflection in most cases is the last option.

Your problem could be solved just providing a key extractor to you method instead of digging properties with Reflection API

Check the code below:

import lombok.Data;

import java.time.Instant;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.List;
import java.util.function.Function;

@Data
public class Task {
    String title;
    Instant date;
    String project;

    public Task(String title, Instant date, String project) {
        this.title = title;
        this.date = date;
        this.project = project;
    }

    @Override
    public String toString() {
        return "Task{" +
                "title='" + title + '\'' +
                ", date=" + date +
                ", project='" + project + '\'' +
                '}';
    }

    public static void sort(Collection<Task> tasks, Function<Task, Comparable> keyExtractor) {
        tasks.stream()
                .sorted(Comparator.comparing(keyExtractor))
                .forEach(System.out::println);
    }

    public static void main(String[] args) {
        List<Task> tasks = new ArrayList<>(3);
        tasks.add(new Task("title1", Instant.now().minusMillis(3), "project3"));
        tasks.add(new Task("title2", Instant.now().minusMillis(1), "project2"));
        tasks.add(new Task("title3", Instant.now().minusMillis(2), "project1"));

        System.out.println("Sorted by title");
        sort(tasks, Task::getTitle);
        System.out.println("Sorted by date");
        sort(tasks, Task::getDate);
        System.out.println("Sorted by project");
        sort(tasks, Task::getProject);

    }

}

The output executing main is:

    Sorted by title
    Task{title='title1', date=2019-10-09T13:42:04.301Z, project='project3'}
    Task{title='title2', date=2019-10-09T13:42:04.303Z, project='project2'}
    Task{title='title3', date=2019-10-09T13:42:04.302Z, project='project1'}
    Sorted by date
    Task{title='title1', date=2019-10-09T13:42:04.301Z, project='project3'}
    Task{title='title3', date=2019-10-09T13:42:04.302Z, project='project1'}
    Task{title='title2', date=2019-10-09T13:42:04.303Z, project='project2'}
    Sorted by project
    Task{title='title3', date=2019-10-09T13:42:04.302Z, project='project1'}
    Task{title='title2', date=2019-10-09T13:42:04.303Z, project='project2'}
    Task{title='title1', date=2019-10-09T13:42:04.301Z, project='project3'}

Upvotes: 2

Marco13
Marco13

Reputation: 54639

The answer that you linked to basically contains the core idea, even though it refers to fields ("properties") and not to methods: Create a way of obtaining the desired value from the object, and then simply compare these values for two objects.

One could consider it as a duplicate, but I'm not sure.

In any case:

You should carefully think about whether reflection is the right approach here.

It might be much more elegant (i.e. less hacky) to generate the required comparators without reflection. This could be an enum where you attach the proper Comparator to each enum value:

enum TaskProperty {
    TITLE(comparatorForTitle), 
    DATE(comparatorForDate), ...
}

// Using it:
tasks.stream().sorted(TaskProperty.TITLE.getComparator()).forEach(...);

Or maybe (less type safe, but a tad more flexible), using a map where you can look them up via a string, as in

// Put all comparators into a map
map.put("getDate", compareTaskByDate);
...
// Later, use the string to pick up the comparator from the map:
tasks.stream().sorted(map.get("getDate")).forEach(...);

If you have carefully thought this through, and really want to use the reflection based approach:

You can create a utility method that obtains the return value of a certain method for a given object. Then create a comparator that calls this method for the given objects, and compares the return values. For flexibility sake, you can pass the return values to a downstream comparator (which can be naturalOrder by default).

An example of how this could be done is shown here. But the list of exceptions that are caught in getOptional should make clear that many things can go wrong here, and you should really consider a different approach:

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;

public class SortWithReflection
{
    public static void main(String[] args)
    {
        List<Task> tasks = new ArrayList<Task>();
        tasks.add(new Task("AAA", 222));
        tasks.add(new Task("BBB", 333));
        tasks.add(new Task("CCC", 111));

        System.out.println("By getTitle:");
        tasks.stream()
            .sorted(by("getTitle"))
            .forEach(System.out::println);

        System.out.println("By getDate:");
        tasks.stream()
            .sorted(by("getDate"))
            .forEach(System.out::println);

        System.out.println("By getDate, reversed");
        tasks.stream()
            .sorted(by("getDate", Comparator.naturalOrder().reversed()))
            .forEach(System.out::println);

    }

    private static <T> Comparator<T> by(String methodName)
    {
        return by(methodName, Comparator.naturalOrder());
    }

    private static <T> Comparator<T> by(
        String methodName, Comparator<?> downstream)
    {
        @SuppressWarnings("unchecked")
        Comparator<Object> uncheckedDownstream =
            (Comparator<Object>) downstream;
        return (t0, t1) -> 
        {
            Object r0 = getOptional(t0, methodName);
            Object r1 = getOptional(t1, methodName);
            return uncheckedDownstream.compare(r0, r1);
        };
    }

    private static <T> T getOptional(
        Object instance, String methodName)
    {
        try
        {
            Class<?> type = instance.getClass();
            Method method = type.getDeclaredMethod(methodName);
            Object object = method.invoke(instance);
            @SuppressWarnings("unchecked")
            T result = (T)object;
            return result;
        }
        catch (NoSuchMethodException 
            | SecurityException 
            | IllegalAccessException 
            | IllegalArgumentException 
            | InvocationTargetException
            | ClassCastException e)
        {
            e.printStackTrace();
            return null;
        }
    }


    static class Task
    {
        String title;
        Integer date;

        Task(String title, Integer date)
        {
            this.title = title;
            this.date = date;
        }

        String getTitle()
        {
            return title;
        }

        Integer getDate()
        {
            return date;
        }

        @Override
        public String toString()
        {
            return title + ": " + date;
        }
    }

}

Upvotes: 2

Related Questions