【问题标题】:Sort a List of Objects based on runtime property根据运行时属性对对象列表进行排序
【发布时间】:2012-11-14 02:06:30
【问题描述】:

我有一个ListValue Objects [VO]。这些对象有许多属性和相应的 get/set 方法。我想根据我将在运行时获得的属性对这个List 进行排序。 让我详细解释一下……

我的语音是这样的:

public class Employee {
    String name;
    String id;

    private String getName() {
        return name;
    }

    private String getId() {
        return id;
    }
}

我将在运行时得到一个字符串sortType,它可以是"id""name"。我想根据String 的值对列表进行排序。

我曾尝试同时使用Comparator 和反射,但没有运气。可能是我没有正确使用它。我不想使用条件 if 块分支来创建需要的任何新的特定 Comparator [匿名内部类](在运行时)。还有其他想法吗?


try catch 应该在新类中。这是工作代码。如果您想为Comparator 使用单独的类,请在下面@Bohemian 的评论中找到它。

        String sortType = "name"; // determined at runtime
        Collections.sort(results, new Comparator<Employee>() {
        public int compare(Employee c1, Employee c2) {
            try{
            Method m = c1.getClass().getMethod("get" + StringUtils.capitalize(sortType));
            String s1 = (String)m.invoke(c1);
            String s2 = (String)m.invoke(c2);
            return s1.compareTo(s2);
            }
            catch (Exception e) {
                return 0;
            }
        }
       });

【问题讨论】:

  • 请使用适当的语言标签。
  • 如果您发布使用反射的代码,我们或许可以告诉您出了什么问题……
  • 我已经添加了我尝试过的代码。
  • try 块是否不适用于将由 new Comparator() 创建的内部类?我是在想。我在 compare() 方法的前 3 行遇到错误。
  • 伙计们.. 我应该删除帖子中所有无效的代码吗?这是我的第一篇文章,对程序不太了解。

标签: java list sorting reflection comparator


【解决方案1】:

无论出于何种原因,您都不想使用if 语句...利用java.lang.reflect.Field

String sortType = "name"; // determined at runtime
Comparator<Employee> comparator = new Comparator<Employee>(){
    public int compare(Employee e0, Employee e1){
        try {
            Field sortField = e0.getClass().getField(sortType);
            return sortField.get(e0).toString()
                       .compareTo(sortField.get(e1).toString()));

        } catch(Exception e) {
            throw new RuntimeException(
                "Couldn't get field with name " + sortType, e);
        }
    };
}

但这仅适用于字符串字段 - 下面处理任何类型的字段(或至少,Comparable 字段)

String sortType = "name"; // determined at runtime
Comparator<Employee> comparator = new Comparator<Employee>(){
    public int compare(Employee e0, Employee e1){
        try {
            Field sortField = e0.getClass().getField(sortType);
            Comparable<Object> comparable0
                = (Comparable<Object>) sortField.get(e0);
            Comparable<Object> comparable1
                = (Comparable<Object>) sortField.get(e1);

            return comparable0.compareTo(comparable1);

        } catch(Exception e) {
            throw new RuntimeException(
                          "Couldn't get field with name " + sortType, e);
        }
    };
}

** 但是请注意,不会忽略 String/Character/char 的大小写(尽管它会考虑负数) -例如,这将在小写 "a" 之前排序大写 "Z"。 如果您想同时考虑这两种情况,我看不到解决方法(等待它...)if 声明!

您可以在我的JavaSortAnyTopLevelValueObject Github 项目中看到处理所有这些情况的完整实现,以及以下情况(也可以在依赖项 JAR 中获得):

  • 处理 多个 排序关系 precedence 字段(即:按优先级 0 排序第一个,按优先级 1 排序第二个 [如果有关系],按...排序第三个)
  • 按每个排序参数处理升序/降序排序
  • 处理 {primitiveBoxed PrimitiveString} 的任何顶级、public(或从调用位置可见的修饰符)字段类型
  • 忽略字符大小写

【讨论】:

    【解决方案2】:

    为作业创建Comparator

    public class EmployeeComparator implements Comparator<Employee> {
    
        private final String type;
    
        public EmployeeComparator (String type) {
            this.type = type;
        }
    
        public int compare(Employee e1, Employee e2) {
            if (type.equals("name")) {
                 return e1.getName().compareTo(e2.getName());
            }
            return e1.getId().compareTo(e2.getId());
        }
    
    }
    

    然后使用它

    String type = "name"; // determined at runtime
    Collections.sort(list, new EmployeeComparator(type));
    

    反射版本将是相似的,除了你会在“get”+类型(大写)的对象上寻找一个方法并调用它并将其硬转换为 Comparable 并使用 compareTo(我将尝试显示代码,但我正在使用我的 iPhone,它有点牵强,但这里)

    public class DynamicComparator implements Comparator<Object> {
        private final String type;
        // pass in type capitalised, eg "Name" 
        // ie the getter method name minus the "get"
        public DynamicComparator (String type) {
            this.type = type;
        }
        public int compare(Object o1, Object o2) {
            // try-catch omitted 
            Method m = o1.getClass().getMethod("get" + type);
            String s1 = (String)m.invoke(o1);
            String s2 = (String)m.invoke(o2);
            return s1.compareTo(s2);
        }
    }
    

    好的...下面是如何做到这一点创建一个类,使用匿名类(异常处理,所以代码编译):

    List<?> list;
    final String attribute = "Name"; // for example. Also, this is case-sensitive
    Collections.sort(list, new Comparator<Object>() {
        public int compare(Object o1, Object o2) {
            try {
                Method m = o1.getClass().getMethod("get" + attribute);
                // Assume String type. If different, you must handle each type
                String s1 = (String) m.invoke(o1);
                String s2 = (String) m.invoke(o2);
                return s1.compareTo(s2);
            // simply re-throw checked exceptions wrapped in an unchecked exception
            } catch (SecurityException e) {
                throw new RuntimeException(e); 
            } catch (NoSuchMethodException e) {
                throw new RuntimeException(e);
            } catch (IllegalAccessException e) {
                throw new RuntimeException(e);
            } catch (InvocationTargetException e) {
                throw new RuntimeException(e);
            }
        }
    });
    

    【讨论】:

    • 这看起来又好又简单。谢谢 !!会尝试让您知道。
    • 很棒的人..!!这正是我一直在寻找的!让我试试看。
    • 这个异常处理又让我死了。我试图一次性完成,而不是创建一个类,现在我遇到了各种各样的异常。这是我尝试过的。
    • 您应该创建一个 Comparator 类。这就是允许您在运行时使用指定的排序字段构造 Comparator(通过在构造函数中传递它)。您最近发布的代码不允许以任何方式指定排序字段。 @Bohemian 的代码允许客户端代码通过在 Comparator 的构造函数中传递一个 String 参数来指定排序字段。
    • 我发布的代码在 getMethod 中有一个“排序”变量,可以在运行时传递。我认为创建一个新类是浪费,但我还没有测试过。会告诉你它是否有效。
    【解决方案3】:

    执行以下操作:

    • 从客户端获取字段名称
    • 构建getter的名字->“get”+字段名(首字母大写后)
    • 尝试使用Class.getDeclaredMethod()查找带反射的方法
    • 如果找到,在您的 VO 类的两个实例上调用返回的 Method 对象
    • 使用调用的 getter 方法的结果进行排序

    【讨论】:

    【解决方案4】:

    Keep it simple!

    如果只能选择idname,请使用 if 语句。

    在两个选项之间进行选择。这就是如果被发明的目的。

    或者,如果它有很多属性,那么使用反射,或者首先将数据存储在Map 中。有时Map 比上课更好。特别是如果您的 VO 没有除 getter 和 setter 之外的方法。

    但请注意,在这种情况下使用反射可能是不安全的,因为您的客户端可能会在类似于 SQL 注入的攻击中在 CGI 参数中注入任何术语。

    【讨论】:

    • OP 声明他的类有 许多 属性。我认为他只是展示了其中两个以保持简单。
    • 这就是问题所在。它不仅仅是 id 和 name,我的 VO 相当大,其中包含大约 10 多个属性。
    • 补充说明为什么使用反射。
    • OP 肯定有机会限制输入...在这里看不到任何注入攻击的风险。
    • 好的,如果是所有属性,那么使用反射——或者首先将数据存储在Map 中。有时Map 比对象更好。特别是如果您的 VO 没有除 getter 和 setter 之外的方法。
    猜你喜欢
    • 2021-06-27
    • 2022-01-11
    • 2022-01-05
    • 2019-02-11
    相关资源
    最近更新 更多