为什么一个Java类应该实现Comparable接口?

148

为什么要使用Java的Comparable接口?为什么需要在类中实现Comparable接口?能否给出一个实际场景的例子说明何时需要实现Comparable接口?


这里有一个很好的例子:http://java67.blogspot.com/2012/10/how-to-sort-object-in-java-comparator-comparable-example.html - james.garriss
10个回答

221

这里是一个现实生活中的例子。请注意,String也实现了Comparable

class Author implements Comparable<Author>{
    String firstName;
    String lastName;

    @Override
    public int compareTo(Author other){
        // compareTo should return < 0 if this is supposed to be
        // less than other, > 0 if this is supposed to be greater than 
        // other and 0 if they are supposed to be equal
        int last = this.lastName.compareTo(other.lastName);
        return last == 0 ? this.firstName.compareTo(other.firstName) : last;
    }
}

later..

/**
 * List the authors. Sort them by name so it will look good.
 */
public List<Author> listAuthors(){
    List<Author> authors = readAuthorsFromFileOrSomething();
    Collections.sort(authors);
    return authors;
}

/**
 * List unique authors. Sort them by name so it will look good.
 */
public SortedSet<Author> listUniqueAuthors(){
    List<Author> authors = readAuthorsFromFileOrSomething();
    return new TreeSet<Author>(authors);
}

17
我想注意的是,通常您会想要重写equals(因此也要重写hashCode)以与您的compareTo方法保持一致。例如,如果您希望该类在TreeSet中良好运行,则这是必需的。 - pidge
为什么不直接返回 last - Anirban Nag 'tintinmj'
1
@AnirbanNag'tintinmj' 自动按名字排序,如果姓氏相同。 - OddDev
感谢你对compareTo返回int及其含义进行详细解释,非常有帮助。 - james.garriss
那么 Comparable 接口只是让其他 Java 方法,而不仅仅是用户,可以比较两个对象吗?否则,如果没有它,您可以定义并使用一个 compareTo 方法,我就看不出实现 Comparable 的意义了。 - user3932000
1
@user3932000:没错,这基本上就是所有接口的目的。但请注意,“其他Java方法”包括用户编写的方法!实际上,我要说大多数接口都被用户的代码使用。在更大的代码库中,“你自己”很快就会变成“别人”。 - Enno Shioji

42

Comparable 定义了一种自然排序方式。这意味着当你需要定义一个对象比另一个对象“小”或“大”时,就可以使用它。

假设你有一堆整数,想要将它们排序。那很容易,只需将它们放入已排序的集合中即可,对吧?

TreeSet<Integer> m = new TreeSet<Integer>(); 
m.add(1);
m.add(3);
m.add(2);
for (Integer i : m)
... // values will be sorted

但现在假设我有一些自定义对象,在这些对象中排序对我来说是有意义的,但未定义。例如,我有一些数据,它代表了区域邮政编码和人口密度,我想按密度对它们进行排序:

public class District {
  String zipcode; 
  Double populationDensity;
}

现在最简单的排序方式是通过实现Comparable接口并定义对象的自然顺序,这意味着存在一种标准的方式来定义这些对象的排序。

public class District implements Comparable<District>{
  String zipcode; 
  Double populationDensity;
  public int compareTo(District other)
  {
    return populationDensity.compareTo(other.populationDensity);
  }
}
请注意,您可以通过定义比较器来实现等效的功能。不同之处在于比较器将排序逻辑定义为对象外部的内容。也许在另一个过程中,我需要按邮政编码对相同的对象进行排序 - 在这种情况下,排序并不一定是对象的属性,或者与对象的自然排序不同。例如,您可以使用外部比较器按字母顺序对整数进行自定义排序。
基本上,排序逻辑必须存在于某个地方。它可以是:
- 如果它具有自然可比性(扩展Comparable,例如整数),则在对象本身中。 - 如上例所示,在外部比较器中提供。

好的例子,但它必须是TreeSet<Integer>而不是TreeMap<Integer>,因为后者不存在,TreeMaps始终是<Key,Value>对。顺便说一句,假设的TreeMap<District, Object>只有在District实现了Comparable接口时才能工作,对吧?我还在努力理解这个。 - phil294

15

摘自javadoc:

该接口在实现它的每个类的对象上强加了一个完全有序的排序。此排序被称为类的自然排序,而类的compareTo方法被称为其自然比较方法。

实现此接口的对象的列表(和数组)可以通过Collections.sort(和Arrays.sort)自动排序。 实现此接口的对象可以用作排序映射中的键或排序集合中的元素,无需指定比较器。

编辑:..并将重要的部分加粗。


4
我认为你加粗的那个句子之后的句子同样重要(如果不是更重要)。 - Michael Borgwardt

9

如果一个类实现了Comparable接口,那么你可以拿这个类的两个对象进行比较。某些类,例如一些集合(集合中的排序函数)需要保持对象的顺序,因此它们依赖于这些对象是可比较的(为了排序,你需要知道哪个对象最大等等)。


8
大多数以上的示例展示了如何在compareTo函数中重用现有的可比较对象。如果你想要实现自己的compareTo,比如要比较同一类别下的两个对象,比如一个AirlineTicket对象,你想按照价格(价格越低排名越高)和中转次数(中转次数越少排名越高)排序,你可以按照以下步骤操作:
class AirlineTicket implements Comparable<Cost>
{
    public double cost;
    public int stopovers;
    public AirlineTicket(double cost, int stopovers)
    {
        this.cost = cost; this.stopovers = stopovers ;
    }

    public int compareTo(Cost o)
    {
        if(this.cost != o.cost)
          return Double.compare(this.cost, o.cost); //sorting in ascending order. 
        if(this.stopovers != o.stopovers)
          return this.stopovers - o.stopovers; //again, ascending but swap the two if you want descending
        return 0;            
    }
}

Cost是否有一个名为AirlineTicket的属性cost?对于属性stopovers(在这两个类中)也是如此吗?这非常奇怪... Cost的实现是什么? - Stéphane Millien

6

实现多字段比较的简单方法是使用Guava's ComparisonChain- 然后你可以说

   public int compareTo(Foo that) {
     return ComparisonChain.start()
         .compare(lastName, that.lastName)
         .compare(firstName, that.firstName)
         .compare(zipCode, that.zipCode)
         .result();
   }

替代

  public int compareTo(Person other) {
    int cmp = lastName.compareTo(other.lastName);
    if (cmp != 0) {
      return cmp;
    }
    cmp = firstName.compareTo(other.firstName);
    if (cmp != 0) {
      return cmp;
    }
    return Integer.compare(zipCode, other.zipCode);
  }
}

3

Comparable被用于比较类的实例。我们可以通过许多方式来比较实例,因此需要实现一个compareTo方法以了解我们想要如何(属性)比较实例。

Dog类:

package test;
import java.util.Arrays;

public class Main {

    public static void main(String[] args) {
        Dog d1 = new Dog("brutus");
        Dog d2 = new Dog("medor");
        Dog d3 = new Dog("ara");
        Dog[] dogs = new Dog[3];
        dogs[0] = d1;
        dogs[1] = d2;
        dogs[2] = d3;

        for (int i = 0; i < 3; i++) {
            System.out.println(dogs[i].getName());
        }
        /**
         * Output:
         * brutus
         * medor
         * ara
         */

        Arrays.sort(dogs, Dog.NameComparator);
        for (int i = 0; i < 3; i++) {
            System.out.println(dogs[i].getName());
        }
        /**
         * Output:
         * ara
         * medor
         * brutus
         */

    }
}

Main 类:

package test;

import java.util.Arrays;

public class Main {

    public static void main(String[] args) {
        Dog d1 = new Dog("brutus");
        Dog d2 = new Dog("medor");
        Dog d3 = new Dog("ara");
        Dog[] dogs = new Dog[3];
        dogs[0] = d1;
        dogs[1] = d2;
        dogs[2] = d3;

        for (int i = 0; i < 3; i++) {
            System.out.println(dogs[i].getName());
        }
        /**
         * Output:
         * brutus
         * medor
         * ara
         */

        Arrays.sort(dogs, Dog.NameComparator);
        for (int i = 0; i < 3; i++) {
            System.out.println(dogs[i].getName());
        }
        /**
         * Output:
         * ara
         * medor
         * brutus
         */

    }
}

这是一个很好的Java中使用comparable的例子: http://www.onjava.com/pub/a/onjava/2003/03/12/java_comp.html?page=2

你刚刚复制了主类代码库,而不是Dog类的代码。Dog.NameComparator的代码在哪里? - lordvidex

3
例如,当你想要一个已排序的集合或映射时。

Fernando 的意思是:如果你将实现 Comparable 接口的“物品”存储在一个排序容器类中,那么排序容器类可以自动对这些“物品”进行排序。 - Ian Durkan

2

好的,但为什么不直接定义一个没有实现comparable接口的compareTo()方法呢?例如,一个由其nametemperature定义的City类。

public int compareTo(City theOther)
{
    if (this.temperature < theOther.temperature)
        return -1;
    else if (this.temperature > theOther.temperature)
        return 1;
    else
        return 0;
}

这个不起作用。如果没有实现comparable接口,我会得到一个类转换异常。 - mrtechmaker

2
当你实现Comparable接口时,需要实现compareTo()方法。这样才能比较对象,例如使用ArrayList类的排序方法。你需要一种比较对象的方式来对它们进行排序。所以,你需要在你的类中添加自定义的compareTo()方法,这样就能够将其与ArrayList的排序方法一起使用。compareTo()方法返回-1、0、1。
我刚刚读了Java Head 2.0中相应的章节,仍在学习中。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接