【问题标题】:How to shorten Comparator with multiple fields?如何缩短具有多个字段的比较器?
【发布时间】:2016-05-10 08:29:49
【问题描述】:

我正在尝试在 java8 中编写一个比较器,它依赖于使用 lambda 的两个标准。我有一个List 人。 Person 有这个方法:

Person{
   String getFirstName();
   String getLastName();
   int getHeight();
   Date getBirthday();
}

列表的排序取决于可以根据意愿选择的两个标准。因此,列表可能应该按名字和生日或名字和身高等排序。

我的方法是创建一个 switch-case 块,在其中我查看标准的不同组合。但是这种方法变得太大了。

switch (holder.criteria1) {
        case FIRSTNAME:
            switch (holder.criteria2) {
                case FIRSTNAME:
                    list.sort(Comparator.comparing(Person::getFirstName,
                            Comparator.nullsFirst(String::compareTo)));
                    break;
                case LASTNAME:
                    list.sort(Comparator.comparing(Person::getFirstname,
                            Comparator.nullsFirst(String::compareTo)).thenComparing(
                            Person::getLastName, Comparator.nullsFirst(String::compareTo)));
                    break;
                case HEIGHT:
                    list.sort(Comparator.comparing(Person::getFirstname,
                            Comparator.nullsFirst(String::compareTo)).thenComparing(
                            Person::getHeight, Comparator.nullsFirst(Integer::compareTo)));
                    break;
                case BIRTHDAY:
                    list.sort(Comparator.comparing(Person::getFirstname,
                            Comparator.nullsFirst(String::compareTo)).thenComparing(
                            Person::getBirthday, Comparator.nullsFirst(Date::compareTo)));
                    break;
            }
            break;

我必须对每个案例组合重复此操作。四分之三的条件 2 几乎相同,只是方法名和类型发生了变化。这是一个非常丑陋且冗长的代码,我想以更好的方式重新设计它。

有没有办法通过使用反射来缩短这个时间?

【问题讨论】:

  • 请不要为了缩短代码而引入反射。
  • 为每个标准创建一个比较器,并根据所选选项提供正确的比较器。
  • 第二个开关不应该打开holder.criteria2或什么的吗?
  • 这是“告诉不问原则”的一个很好的例子。您不必查询条件然后决定如何排序;相反,您应该更改代码,以便“标准”知道如何排序。此外:这样的枚举开关不仅又长又丑……你应该完全避免它们。
  • @JornVernee 是的,你是对的。我编辑了我的问题。

标签: java reflection lambda comparator


【解决方案1】:

您可以将比较器存储在枚举值中,然后按需组合:

enum SortOn {
    FIRSTNAME(Comparator.comparing(Person::getFirstName,
        Comparator.nullsFirst(String::compareTo))),
    LASTNAME(Comparator.comparing(Person::getLastName, 
        Comparator.nullsFirst(String::compareTo))),
    HEIGHT(Comparator.comparing(Person::getHeight, 
        Comparator.nullsFirst(Integer::compareTo))),
    BIRTHDAY(Comparator.comparing(Person::getBirthday, 
        Comparator.nullsFirst(Date::compareTo)));

    public final Comparator<Person> comparator;

    private SortOn(Comparator<Person> comparator) {
        this.comparator = comparator;
    }
}
...
public void sort(SortOn criteria1, SortOn criteria2) {
    if(criteria1 == criteria2) {
        list.sort(criteria1.comparator);
    } else {
        list.sort(criteria1.comparator.thenComparing(criteria2.comparator));
    }
}

【讨论】:

    【解决方案2】:

    也许你可以这样做:

    public enum Holder{
     FIRSTNAME{
       @override
        Function getFunction(){
         return Person::getFirstName;
        }
    }
    
     ........
     abstract Function getFunction();
    }
    

    那么你只需要这个调用:

    list.sort(Comparator.comparing(holder1.getFunction(),
                              Comparator.nullsFirst(String::compareTo)).thenComparing(
                            holder2.getFunction(), Comparator.nullsFirst(String::compareTo)));
    

    【讨论】:

      【解决方案3】:

      作为Jorn Vernee’s answer 的补充,这里的派生变体避免了代码重复:

      enum SortOn {
          FIRSTNAME(Person::getFirstName),
          LASTNAME(Person::getLastName),
          HEIGHT(Person::getHeight),
          BIRTHDAY(Person::getBirthday);
      
          public final Comparator<Person> comparator;
      
          private <U extends Comparable<U>> SortOn(Function<Person,U> f) {
              this.comparator = Comparator.comparing(f,
                  Comparator.nullsFirst(Comparator.naturalOrder()));
          }
          private SortOn(ToIntFunction<Person> f) {
              this.comparator = Comparator.comparingInt(f);
          }
      }
      

      由于所有属性都具有Comparable 类型,因此当需要自然顺序时,可以对它们进行类似处理。

      虽然它们都可以以相同的方式工作,但 HEIGHT 实例在此处使用替代构造函数来创建特定于 int 属性的 Comparator,从而避免了装箱开销。由于这些值永远不会是 null,因此对于此特定属性,null-check 也已过时。

      这个枚举仍然可以以同样的方式使用,例如

      public static void sort(List<Person> list, SortOn criteria1, SortOn criteria2) {
          if(criteria1 == criteria2) {
              list.sort(criteria1.comparator);
          } else {
              list.sort(criteria1.comparator.thenComparing(criteria2.comparator));
          }
      }
      

      或支持任意数量的标准:

      public static void sort(List<Person> list, SortOn... criteria) {
          list.sort(Arrays.stream(criteria).map(c -> c.comparator)
              .reduce(Comparator::thenComparing)
              .orElseThrow(() -> new IllegalArgumentException("no criteria given")));
      }
      

      【讨论】:

        猜你喜欢
        • 2020-07-07
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2010-09-27
        • 2012-07-22
        相关资源
        最近更新 更多