假设你有一些对象,它们有几个字段可以比较:

public class Person {

    private String firstName;
    private String lastName;
    private String age;

    /* Constructors */

    /* Methods */

}

所以在这个例子中,当你问if:

a.compareTo(b) > 0

你可能会问a的姓是不是在b的姓之前,或者a的年龄是不是比b大,等等……

在不增加不必要的混乱或开销的情况下,在这些类型的对象之间进行多重比较的最干净的方法是什么?

comparable接口只允许通过一个字段进行比较 在我看来,添加大量的比较方法(如compareByFirstName(), compareByAge()等)是混乱的。

那么最好的解决办法是什么呢?


当前回答

Java 8通过lambda方式我们可以通过方法引用进行比较。 学生POJO

public class Student {
int id;
String firstName;
String lastName;
String subject;

public Student(int id, String firstName, String lastName, String subject) {
    this.id = id;
    this.firstName = firstName;
    this.lastName = lastName;
    this.subject = subject;
}
enter code here

现在我们可以根据

1. id - > FirstName - > LastName - > 2。主题- > id - > FirstName - > LastName

我们将在数组Stream中使用Comparator

public class TestComprator {
public static void main(String[] args) {
    Student s1= new Student(108, "James", "Testo", "Physics");
    Student s2= new Student(101, "Fundu", "Barito", "Chem");
    Student s3= new Student(105, "Sindhu", "Sharan", "Math");
    Student s4= new Student(98, "Rechel", "Stephen", "Physics");
    System.out.printf("----------id->FirstName->LastName->Subject-------------");
    Arrays.asList(s1,s2,s3,s4).stream()
            .sorted(Comparator.comparing(Student::getId)
                    .thenComparing(Student::getFirstName)
                .thenComparing(Student::getLastName)
                .thenComparing(Student::getSubject))
            .forEach(System.out::println);

    System.out.printf("----Subject->id->FirstName->LastName ------\n");
    Arrays.asList(s1,s2,s3,s4).stream()
            .sorted(Comparator. comparing(Student::getSubject)
                    .thenComparing(Student::getId)
                    .thenComparing(Student::getFirstName)
                    .thenComparing(Student::getLastName)
                   )
            .forEach(System.out::println);
}

}

输出:

`----------id->FirstName->LastName->Subject-------------
Student{id=98, firstName='Rechel', lastName='Stephen', subject='Physics'}
Student{id=101, firstName='Fundu', lastName='Barito', subject='Chem'}
Student{id=105, firstName='Sindhu', lastName='Sharan', subject='Math'}
Student{id=108, firstName='James', lastName='Testo', subject='Physics'}
 ----Subject->id->FirstName->LastName ------
Student{id=101, firstName='Fundu', lastName='Barito', subject='Chem'}
Student{id=105, firstName='Sindhu', lastName='Sharan', subject='Math'}
Student{id=98, firstName='Rechel', lastName='Stephen', subject='Physics'}
Student{id=108, firstName='James', lastName='Testo', subject='Physics'}

其他回答

我认为如果你的比较算法是“聪明的”,那就更令人困惑了。我会选择你建议的众多比较方法。

对我来说唯一的例外就是平等。对于单元测试,重写. equals(在.net中)对我来说很有用,以便确定两个对象之间的几个字段是否相等(而不是引用是否相等)。

(来自Java中基于多个字段对对象列表进行排序的方法)

工作代码在这个要点

使用Java 8 lambda(2019年4月10日添加)

Java 8通过lambda很好地解决了这个问题(尽管Guava和Apache Commons可能仍然提供了更大的灵活性):

Collections.sort(reportList, Comparator.comparing(Report::getReportKey)
            .thenComparing(Report::getStudentNumber)
            .thenComparing(Report::getSchool));

感谢@高公的回答。

杂乱而复杂:手工分类

Collections.sort(pizzas, new Comparator<Pizza>() {  
    @Override  
    public int compare(Pizza p1, Pizza p2) {  
        int sizeCmp = p1.size.compareTo(p2.size);  
        if (sizeCmp != 0) {  
            return sizeCmp;  
        }  
        int nrOfToppingsCmp = p1.nrOfToppings.compareTo(p2.nrOfToppings);  
        if (nrOfToppingsCmp != 0) {  
            return nrOfToppingsCmp;  
        }  
        return p1.name.compareTo(p2.name);  
    }  
});  

这需要大量的输入和维护,而且很容易出错。

反射方式:用BeanComparator排序

ComparatorChain chain = new ComparatorChain(Arrays.asList(
   new BeanComparator("size"), 
   new BeanComparator("nrOfToppings"), 
   new BeanComparator("name")));

Collections.sort(pizzas, chain);  

显然,这更简洁,但更容易出错,因为使用string而失去了对字段的直接引用(没有类型安全,自动重构)。现在,如果字段被重命名,编译器甚至不会报告问题。此外,由于该解决方案使用反射,排序要慢得多。

到达那里:排序谷歌番石榴的ComparisonChain

Collections.sort(pizzas, new Comparator<Pizza>() {  
    @Override  
    public int compare(Pizza p1, Pizza p2) {  
        return ComparisonChain.start().compare(p1.size, p2.size).compare(p1.nrOfToppings, p2.nrOfToppings).compare(p1.name, p2.name).result();  
        // or in case the fields can be null:  
        /* 
        return ComparisonChain.start() 
           .compare(p1.size, p2.size, Ordering.natural().nullsLast()) 
           .compare(p1.nrOfToppings, p2.nrOfToppings, Ordering.natural().nullsLast()) 
           .compare(p1.name, p2.name, Ordering.natural().nullsLast()) 
           .result(); 
        */  
    }  
});  

这样好多了,但对于最常见的用例,需要一些样板代码:默认情况下,null值的值应该更小。对于空字段,您必须向Guava提供一个额外的指令,在这种情况下要做什么。如果你想做一些特定的事情,这是一个灵活的机制,但通常你想要默认的情况(即。1, a, b, z, null)。

使用Apache Commons CompareToBuilder进行排序

Collections.sort(pizzas, new Comparator<Pizza>() {  
    @Override  
    public int compare(Pizza p1, Pizza p2) {  
        return new CompareToBuilder().append(p1.size, p2.size).append(p1.nrOfToppings, p2.nrOfToppings).append(p1.name, p2.name).toComparison();  
    }  
});  

像Guava的ComparisonChain一样,这个库类很容易在多个字段上排序,但也为空值定义了默认行为。1, a, b, z, null)。但是,您也不能指定任何其他内容,除非您提供自己的Comparator。

Thus

最终,这归结于口味和灵活性的需要(Guava的ComparisonChain) vs.简洁的代码(Apache的CompareToBuilder)。

额外的方法

我发现了一个很好的解决方案,在MultiComparator的CodeReview中按优先级顺序组合多个比较器:

class MultiComparator<T> implements Comparator<T> {
    private final List<Comparator<T>> comparators;

    public MultiComparator(List<Comparator<? super T>> comparators) {
        this.comparators = comparators;
    }

    public MultiComparator(Comparator<? super T>... comparators) {
        this(Arrays.asList(comparators));
    }

    public int compare(T o1, T o2) {
        for (Comparator<T> c : comparators) {
            int result = c.compare(o1, o2);
            if (result != 0) {
                return result;
            }
        }
        return 0;
    }

    public static <T> void sort(List<T> list, Comparator<? super T>... comparators) {
        Collections.sort(list, new MultiComparator<T>(comparators));
    }
}

当然Apache Commons Collections已经有了一个util:

ComparatorUtils.chainedComparator (comparatorCollection)

Collections.sort(list, ComparatorUtils.chainedComparator(comparators));

从Steve的回答开始,三元操作符可以使用:

public int compareTo(Person other) {
    int f = firstName.compareTo(other.firstName);
    int l = lastName.compareTo(other.lastName);
    return f != 0 ? f : l != 0 ? l : Integer.compare(age, other.age);
}

下面的博客给出了一个很好的链式比较器的例子

http://www.codejava.net/java-core/collections/sorting-a-list-by-multiple-attributes-example

import java.util.Arrays;
import java.util.Comparator;
import java.util.List;

/**
 * This is a chained comparator that is used to sort a list by multiple
 * attributes by chaining a sequence of comparators of individual fields
 * together.
 *
 */
public class EmployeeChainedComparator implements Comparator<Employee> {

    private List<Comparator<Employee>> listComparators;

    @SafeVarargs
    public EmployeeChainedComparator(Comparator<Employee>... comparators) {
        this.listComparators = Arrays.asList(comparators);
    }

    @Override
    public int compare(Employee emp1, Employee emp2) {
        for (Comparator<Employee> comparator : listComparators) {
            int result = comparator.compare(emp1, emp2);
            if (result != 0) {
                return result;
            }
        }
        return 0;
    }
}

打电话比较器:

Collections.sort(listEmployees, new EmployeeChainedComparator(
                new EmployeeJobTitleComparator(),
                new EmployeeAgeComparator(),
                new EmployeeSalaryComparator())
        );

在java中使用hashcode方法比较两个对象很容易。

public class Sample{

  String a=null;
  String b=null;

  public Sample(){
      a="s";
      b="a";
  }
  public Sample(String a,String b){
      this.a=a;
      this.b=b;
  }
  public static void main(String args[]){
      Sample f=new Sample("b","12");
      Sample s=new Sample("b","12");
      //will return true
      System.out.println((s.a.hashCode()+s.b.hashCode())==(f.a.hashCode()+f.b.hashCode()));

      //will return false
      Sample f=new Sample("b","12");
      Sample s=new Sample("b","13");
      System.out.println((s.a.hashCode()+s.b.hashCode())==(f.a.hashCode()+f.b.hashCode()));

}