【问题标题】:Streams Sorting by runtime parameter流按运行时参数排序
【发布时间】:2020-04-07 04:49:19
【问题描述】:

我有自定义比较器ChainedComparator,它有多个比较器,将在运行时传递。

class ChainedComparator implements Comparator<Person> {
    private List<Comparator<Person>> comparatorList;

    public int compare(Person o1, Person o2) {
        for (Comparator<Person> comparator : comparatorList) {
            int result = comparator.compare(o1, o2);
            if (result != 0) {
                return result;
            }
        }
        return 0;

    }

    @SafeVarargs
    public ChainedComparator(Comparator<Person>... comparators) {
        this.comparatorList = Arrays.asList(comparators);
    }
}

现在我想在运行时从字段名称 age 创建比较器,而不是像下面这样的硬编码

Comparator<Person> ageComparator = Comparator.comparing(Person::getAge);
Comparator<Person> lastNameComparator = Comparator.comparing(Person::getLastName);
Comparator<Person> ageComparator = Comparator.comparing(Person::getAge);
personList.stream().sorted(new ChainedComparator(firstNameComparator,ageComparator))

请给点建议

class Person {
    Person(){}
    String firstName;
    String lastName;
    int age;
    String country;
    public Person( String firstName, String lastName, int age,String country) {
        super();
        this.firstName = firstName;
        this.lastName = lastName;
        this.age = age;
        this.country = country;

    }

    // getters and setters
}

【问题讨论】:

  • 有点不清楚。您是说在运行之前您不会知道要比较的字段吗?
  • 是的,我会在运行时获得年龄、名字等排序字段列表,我想在运行时创建比较器并将它们链接起来
  • 我可以使用 switch 或 if 子句来检查字段名称并添加相应的比较器。但我想是通用的,我传递文件名,然后它获取方法引用并创建比较器

标签: java sorting reflection collections java-stream


【解决方案1】:

最简单的静态方法可能是只维护一个字段名称 -> 比较器映射,如下所示:

private static Map<String, Comparator<Person>> comparatorMap = Map.of(
        "age", Comparator.comparing(Person::getAge),
        "firstName", Comparator.comparing(Person::getFirstName),
        "lastName", Comparator.comparing(Person::getLastName),
        "country", Comparator.comparing(Person::getCountry)
    );

其中,给定一个像

这样的字段列表
List<String> sortFields = Arrays.asList("age", "firstName", "lastName");

可用于生成组合比较器:

Optional<Comparator<Person>> chainedComparator = 
    sortFields.stream().map(comparatorMap::get).reduce(Comparator::thenComparing);

personList.stream().sorted(chainedComparator.get());

您还可以注意到,我使用了Comparator 自己的thenComparing 来编写比较器(而不是自己实现)。


这是一个使用反射来动态选择要排序的字段的示例实现

使用前请注意: * 它假设 fields 是 Comparable 类型(如果你传递一个类型不可比较的字段的名称,则会引发类转换异常) * 我每次都在创建一个比较器,如果有必要,您可能希望将它们存储在字段名称中

private static Comparator<Person> personFieldComparator(String field) {
    return (person1, person2) -> readPersonField(person1, field)
            .compareTo(readPersonField(person2, field));
}

private static Comparable<Object> readPersonField(Person person, String field) {

    try {
        Field f = Person.class.getDeclaredField(field);
        f.setAccessible(true);

        return (Comparable<Object>) f.get(person);
    } catch (IllegalAccessException | IllegalArgumentException
             | NoSuchFieldException e) {
        throw new RuntimeException(e); //proper handling needed
    }
}

然后,您可以将初始实现更改为:

Optional<Comparator<Person>> chainedComparator = sortFields.stream()
        .map(Main::personFieldComparator) //Main -> Your class name
        .reduce(Comparator::thenComparing);
personList.stream().sorted(chainedComparator.orElseThrow())...;

您可以选择通过使用泛型来扩展它,使其不是Person-specific

【讨论】:

  • 感谢您的回复。有什么方法可以在运行时生成这个Person::getAge,可能是使用反射或其他方式。
  • 当然可以使用反射,但我绝对更喜欢静态、类型安全的代码。如果您想动态访问字段,您是否考虑过使用哈希映射而不是类?
  • 同意。静态更适合类型安全。试图了解如何在运行时创建它,例如Comparator.comparing( new Person().getClass().getMethod("age")。但无法获得正确的语法
  • 如果你需要,我可以使用反射添加一个替代方案...给我一点时间
  • 看this answer的结尾…
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-06-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多