流按运行时参数排序

huangapple go评论103阅读模式
英文:

Streams Sorting by runtime parameter

问题

我有一个自定义的比较器 ChainedComparator,其中包含多个比较器,这些比较器将在运行时传递。

  1. class ChainedComparator implements Comparator<Person> {
  2. private List<Comparator<Person>> comparatorList;
  3. public int compare(Person o1, Person o2) {
  4. for (Comparator<Person> comparator : comparatorList) {
  5. int result = comparator.compare(o1, o2);
  6. if (result != 0) {
  7. return result;
  8. }
  9. }
  10. return 0;
  11. }
  12. @SafeVarargs
  13. public ChainedComparator(Comparator<Person>... comparators) {
  14. this.comparatorList = Arrays.asList(comparators);
  15. }
  16. }

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

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

有什么建议吗?

  1. class Person {
  2. Person() {}
  3. String firstName;
  4. String lastName;
  5. int age;
  6. String country;
  7. public Person(String firstName, String lastName, int age, String country) {
  8. super();
  9. this.firstName = firstName;
  10. this.lastName = lastName;
  11. this.age = age;
  12. this.country = country;
  13. }
  14. // 获取器和设置器
  15. }

(以上内容为您要翻译的部分。)

英文:

I have custom Comparator ChainedComparator and and it has multiple comparator in it, which will be passed at runtime.

  1. class ChainedComparator implements Comparator&lt;Person&gt; {
  2. private List&lt;Comparator&lt;Person&gt;&gt; comparatorList;
  3. public int compare(Person o1, Person o2) {
  4. for (Comparator&lt;Person&gt; comparator : comparatorList) {
  5. int result = comparator.compare(o1, o2);
  6. if (result != 0) {
  7. return result;
  8. }
  9. }
  10. return 0;
  11. }
  12. @SafeVarargs
  13. public ChainedComparator(Comparator&lt;Person&gt;... comparators) {
  14. this.comparatorList = Arrays.asList(comparators);
  15. }
  16. }

now i want to create comparator at run time from the field name age , rather than hardcoding like below

  1. Comparator&lt;Person&gt; ageComparator = Comparator.comparing(Person::getAge);
  2. Comparator&lt;Person&gt; lastNameComparator = Comparator.comparing(Person::getLastName);
  3. Comparator&lt;Person&gt; ageComparator = Comparator.comparing(Person::getAge);
  4. personList.stream().sorted(new ChainedComparator(firstNameComparator,ageComparator))

any advice please

  1. class Person {
  2. Person(){}
  3. String firstName;
  4. String lastName;
  5. int age;
  6. String country;
  7. public Person( String firstName, String lastName, int age,String country) {
  8. super();
  9. this.firstName = firstName;
  10. this.lastName = lastName;
  11. this.age = age;
  12. this.country = country;
  13. }
  14. // getters and setters
  15. }

答案1

得分: 4

最简单静态的方式可能是维护一个字段名到比较器映射,就像这样:

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

通过给定类似于以下的字段列表:

  1. List<String> sortFields = Arrays.asList("age", "firstName", "lastName");

可以用来生成一个复合的比较器:

  1. Optional<Comparator<Person>> chainedComparator =
  2. sortFields.stream().map(comparatorMap::get).reduce(Comparator::thenComparing);
  3. personList.stream().sorted(chainedComparator.get());

你还可以注意到我使用了 Comparator 自己的 thenComparing 来组合比较器(而不是自己实现它)。


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

在使用之前,请注意:

  • 它假设字段Comparable类型(如果你传递了类型不可比较的字段名称,将引发类转换异常)
  • 每次我都在创建一个比较器,如果有必要,你可能想根据字段名存储它们
  1. private static Comparator<Person> personFieldComparator(String field) {
  2. return (person1, person2) -> readPersonField(person1, field)
  3. .compareTo(readPersonField(person2, field));
  4. }
  5. private static Comparable<Object> readPersonField(Person person, String field) {
  6. try {
  7. Field f = Person.class.getDeclaredField(field);
  8. f.setAccessible(true);
  9. return (Comparable<Object>) f.get(person);
  10. } catch (IllegalAccessException | IllegalArgumentException
  11. | NoSuchFieldException e) {
  12. throw new RuntimeException(e); //proper handling needed
  13. }
  14. }

有了这些,你可以将初始实现更改为类似以下的内容:

  1. Optional<Comparator<Person>> chainedComparator = sortFields.stream()
  2. .map(Main::personFieldComparator) //Main -&gt; 你的类名
  3. .reduce(Comparator::thenComparing);
  4. personList.stream().sorted(chainedComparator.orElseThrow())...;

你可以通过使用泛型来扩展,以使其不仅限于 Person 类。

英文:

The simplest, static way to do this may be to just maintain a field name -> Comparator map, like this:

  1. private static Map&lt;String, Comparator&lt;Person&gt;&gt; comparatorMap = Map.of(
  2. &quot;age&quot;, Comparator.comparing(Person::getAge),
  3. &quot;firstName&quot;, Comparator.comparing(Person::getFirstName),
  4. &quot;lastName&quot;, Comparator.comparing(Person::getLastName),
  5. &quot;country&quot;, Comparator.comparing(Person::getCountry)
  6. );

Which, given a field list like

  1. List&lt;String&gt; sortFields = Arrays.asList(&quot;age&quot;, &quot;firstName&quot;, &quot;lastName&quot;);

can be used to produce a composed comparator:

  1. Optional&lt;Comparator&lt;Person&gt;&gt; chainedComparator =
  2. sortFields.stream().map(comparatorMap::get).reduce(Comparator::thenComparing);
  3. personList.stream().sorted(chainedComparator.get());

You can also notice that I used Comparator's own thenComparing to compose comparators (instead of implementing it yourself).


Here's an example implementation using reflection to dynamically select the fields to sort by

Before it's used, note:

  • It assumes that fields are of Comparable types (if you pass the name of a field whose type is not comparable, a class cast exception will be raised)

  • I'm creating a comparator each time, you may want to store them against the field name, if that's necessary

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

    1. try {
    2. Field f = Person.class.getDeclaredField(field);
    3. f.setAccessible(true);
    4. return (Comparable&lt;Object&gt;) f.get(person);
    5. } catch (IllegalAccessException | IllegalArgumentException
    6. | NoSuchFieldException e) {
    7. throw new RuntimeException(e); //proper handling needed
    8. }

    }

And with that, you can change the initial implementation to something like:

  1. Optional&lt;Comparator&lt;Person&gt;&gt; chainedComparator = sortFields.stream()
  2. .map(Main::personFieldComparator) //Main -&gt; Your class name
  3. .reduce(Comparator::thenComparing);
  4. personList.stream().sorted(chainedComparator.orElseThrow())...;

You may choose to extend this by using generics to make it not Person-specific

huangapple
  • 本文由 发表于 2020年4月7日 12:49:19
  • 转载请务必保留本文链接:https://go.coder-hub.com/61073018.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定