首页 > 解决方案 > 在 Comparator 比较方法中调用方法

问题描述

我有一个要打印的 ArrayList< Task > 命名任务,根据Task对象的每个字段进行排序。任务类有三个私有字段 { String title , Date date , String project }。该类有一些公共的 get 方法,允许其他类读取字段 { getTitle()getDate()getProject()getTaskDetails() }。

我有一个简单的方法,它使用流对 ArryaList 任务进行排序和打印:

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

我想使用反射 API,而不是创建 3 种不同的方法来根据每个不同的 getter 方法进行排序。但我无法在 Comparator 比较方法中调用方法( getTitle()getDate()getProject() ):

用过的import java.lang.reflect.Method;

声明方法Method taskMethod = Task.class.getDeclaredMethod(methodName);,其中methodName将是使用方法名称(“getTitle”“getDate”“getProject”)接收的参数字符串。

然后尝试做这样的事情,但没有锻炼:

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

这可能吗?或者有没有更简单的解决方案?

只发现了这个问题,但没有解决我的问题。

感谢您的任何反馈。

标签: javareflectioncomparator

解决方案


您链接到的答案基本上包含核心思想,即使它引用字段(“属性”)而不是方法:创建一种从对象获取所需值的方法,然后简单地比较两个对象的这些值。

人们可以认为它是重复的,但我不确定。

任何状况之下:

您应该仔细考虑反射是否是正确的方法。

在没有反射的情况下生成所需的比较器可能会更优雅(即更少hacky)。这可能是一个枚举,您可以在其中将正确的属性附加Comparator到每个枚举值:

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

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

或者也许(更少类型安全,但更灵活一点),使用可以通过字符串查找它们的地图,如

// 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(...);

如果您仔细考虑了这一点,并且真的想使用基于反射的方法:

您可以创建一个实用方法来获取给定对象的某个方法的返回值。然后创建一个为给定对象调用此方法的比较器,并比较返回值。为了灵活起见,您可以将返回值传递给下游比较器(naturalOrder默认情况下)。

此处显示了如何完成此操作的示例。但是捕获的异常列表getOptional应该清楚地表明很多事情都可能在这里出错,您应该真正考虑一种不同的方法:

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

}

推荐阅读