Java中Comparable和Comparator你知多少?

时间:2023-03-09 09:30:26
Java中Comparable和Comparator你知多少?

前言:

我喜欢这种遨游在Java的世界里,精心研究学习新鲜事物的感觉,即便再小再细再微不足道的东西,也让我乐此不疲,同时我也更愿意将我所会的东西分享出来供大家学习以及方便自己日后回顾。好了,闲话不多说,今天主要是介绍Comparable和Comparator俩个接口(参照JavaAPI)以及差异,并且通过一些示例进行说明。


Comparable

1.简介:

此接口强行对实现它的每个类的对象进行整体排序。这种排序被称为类的自然排序,类的 compareTo 方法被称为它的自然比较方法。

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

对于类 C 的每一个 e1 和 e2 来说,当且仅当 e1.compareTo(e2) == 0 与 e1.equals(e2) 具有相同的 boolean 值时,类 C 的自然排序才叫做与 equals 一致。注意,null 不是任何类的实例,即使 e.equals(null) 返回 falsee.compareTo(null) 也将抛出 NullPointerException

建议(虽然不是必需的)最好使自然排序与 equals 一致。这是因为在使用自然排序与 equals 不一致的元素(或键)时,没有显式比较器的有序集合(和有序映射表)行为表现“怪异”。尤其是,这样的有序集合(或有序映射表)违背了根据 equals 方法定义的集合(或映射表)的常规协定。

例如,如果将两个键 a 和 b 添加到没有使用显式比较器的有序集合中,使 (!a.equals(b) && a.compareTo(b) == 0),那么第二个 add 操作将返回 false(有序集合的大小没有增加),因为从有序集合的角度来看,a 和 b 是相等的。

实际上,所有实现 Comparable 的 Java 核心类都具有与 equals 一致的自然排序。java.math.BigDecimal 是个例外,它的自然排序将值相等但精确度不同的BigDecimal 对象(比如 4.0 和 4.00)视为相等。

从数学上讲,定义给定类 C 上自然排序的关系式 如下:

      {(x, y)|x.compareTo(y) <= 0}。

整体排序的  是:

      {(x, y)|x.compareTo(y) == 0}。

它直接遵循 compareTo 的协定,商是 C 的 等价关系,自然排序是 C 的 整体排序。当说到类的自然排序 与 equals 一致 时,是指自然排序的商是由类的equals(Object) 方法定义的等价关系。

    {(x, y)|x.equals(y)}。

此接口是 Java Collections Framework 的成员。

2.所有已知实现该接口的类:

Authenticator.RequestorTypeBigDecimalBigIntegerBooleanByteByteBufferCalendarCharacterCharBufferCharset,ClientInfoStatusCollationKeyComponent.BaselineResizeBehaviorCompositeNameCompoundNameDateDateDesktop.Action,Diagnostic.KindDialog.ModalExclusionTypeDialog.ModalityTypeDoubleDoubleBufferDropModeElementKindElementTypeEnum,FileFloatFloatBufferFormatter.BigDecimalLayoutFormFormSubmitEvent.MethodTypeGregorianCalendarGroupLayout.Alignment,IntBufferIntegerJavaFileObject.KindJTable.PrintModeKeyRep.TypeLayoutStyle.ComponentPlacementLdapNameLongLongBuffer,MappedByteBufferMemoryTypeMessageContext.ScopeModifierMultipleGradientPaint.ColorSpaceType,MultipleGradientPaint.CycleMethodNestingKindNormalizer.FormObjectNameObjectStreamFieldProxy.TypeRdn,Resource.AuthenticationTypeRetentionPolicyRoundingModeRowFilter.ComparisonTypeRowIdLifetimeRowSorterEvent.Type,Service.ModeShortShortBufferSOAPBinding.ParameterStyleSOAPBinding.StyleSOAPBinding.UseSortOrderSourceVersion,SSLEngineResult.HandshakeStatusSSLEngineResult.StatusStandardLocationStringSwingWorker.StateValueThread.StateTime,TimestampTimeUnitTrayIcon.MessageTypeTypeKindURIUUIDWebParam.ModeXmlAccessOrderXmlAccessTypeXmlNsForm

3.所有已知子接口:

DelayedNameRunnableScheduledFuture<V>, ScheduledFuture<V>

4.接口中方法介绍:

该接口只有一个方法:

package java.lang;
import java.util.*; public interface Comparable<T> {
public int compareTo(T o);
}

说明:
假设我们通过 x.compareTo(y) 来“比较x和y的大小”。若返回“负数”,意味着“x比y小”;返回“零”,意味着“x等于y”;返回“正数”,意味着“x大于y”。 


Comparator

1.简介:

强行对某个对象 collection 进行整体排序的比较函数。可以将 Comparator 传递给 sort 方法(如 Collections.sort 或 Arrays.sort),从而允许在排序顺序上实现精确控制。还可以使用 Comparator 来控制某些数据结构(如有序 set有序映射)的顺序,或者为那些没有自然顺序的对象 collection 提供排序。

当且仅当对于一组元素 S 中的每个 e1 和 e2 而言,c.compare(e1, e2)==0 与 e1.equals(e2) 具有相等的布尔值时,Comparator c 强行对 S 进行的排序才叫做与 equals 一致 的排序。

当使用具有与 equals 不一致的强行排序能力的 Comparator 对有序 set(或有序映射)进行排序时,应该小心谨慎。假定一个带显式 Comparator c 的有序 set(或有序映射)与从 set S 中抽取出来的元素(或键)一起使用。如果 c 强行对 S 进行的排序是与 equals 不一致的,那么有序 set(或有序映射)将是行为“怪异的”。尤其是有序 set(或有序映射)将违背根据 equals 所定义的 set(或映射)的常规协定。

例如,假定使用 Comparator c 将满足 (a.equals(b) && c.compare(a, b) != 0) 的两个元素 a 和 b 添加到一个空 TreeSet 中,则第二个 add 操作将返回 true(树 set 的大小将会增加),因为从树 set 的角度来看,a 和 b 是不相等的,即使这与 Set.add 方法的规范相反。

注:通常来说,让 Comparator 也实现 java.io.Serializable 是一个好主意,因为它们在可序列化的数据结构(像 TreeSetTreeMap)中可用作排序方法。为了成功地序列化数据结构,Comparator(如果已提供)必须实现 Serializable

在算术上,定义给定 Comparator c 对给定对象 set S 实施强行排序 的关系式 为:

       {(x, y) such that c.compare(x, y) <= 0}.

此整体排序的 商 (quotient) 为:

       {(x, y) such that c.compare(x, y) == 0}.
 

它直接遵循 compare 的协定,商是 S 上的 等价关系,强行排序是 S 上的 整体排序。当我们说 c 强行对 S 的排序是 与 equals 一致 的时,意思是说排序的商是对象的 equals(Object) 方法所定义的等价关系:

       {(x, y) such that x.equals(y)}. 

此接口是 Java Collections Framework 的成员。

2.接口中方法介绍:

该接口有俩个方法:

package java.util;

public interface Comparator<T> {

    int compare(T o1, T o2);

    boolean equals(Object obj);
}

说明:
(1) 若一个类要实现Comparator接口:它一定要实现compareTo(T o1, T o2) 函数,但可以不实现 equals(Object obj) 函数。

因为任何类,默认都是已经实现了equals(Object obj)的。 Java中的一切类都是继承于java.lang.Object,在Object.java中实现了equals(Object obj)函数;所以,其它所有的类也相当于都实现了该函数。

(2) int compare(T o1, T o2) 是“比较o1和o2的大小”。返回“负数”,意味着“o1比o2小”;返回“零”,意味着“o1等于o2”;返回“正数”,意味着“o1大于o2”。


Comparator 和 Comparable 比较

Comparable是排序接口;若一个类实现了Comparable接口,就意味着“该类支持排序”。
而Comparator是比较器;我们若需要控制某个类的次序,可以建立一个“该类的比较器”来进行排序。

我们不难发现:Comparable相当于“内部比较器”,而Comparator相当于“外部比较器”。


最后通过测试程序来对俩个接口进行说明:

Comparable接口测试代码:

public class Book implements Comparable { // 定义名为Book的类,默认继承自Object类
public int id;// 编号
public String name;// 名称
public double price; // 价格
private String author;// 作者
public GregorianCalendar calendar;// 出版日期 public Book() {
this(0, "X", 0.0, new GregorianCalendar(), "");
} public Book(int id, String name, double price, GregorianCalendar calender,
String author) {
this.id = id;
this.name = name;
this.price = price;
this.calendar = calender;
this.author = author;
} // 重写继承自父类Object的方法,满足Book类信息描述的要求
public String toString() {
String showStr = id + "\t" + name; // 定义显示类信息的字符串
DecimalFormat formatPrice = new DecimalFormat("0.00");// 格式化价格到小数点后两位
showStr += "\t" + formatPrice.format(price);// 格式化价格
showStr += "\t" + author;
SimpleDateFormat formatDate = new SimpleDateFormat("yyyy年MM月dd日");
showStr += "\t" + formatDate.format(calendar.getTime()); // 格式化时间
return showStr; // 返回类信息字符串
} public int compareTo(Object obj) {// Comparable接口中的方法
Book b = (Book) obj;
return (int) (this.price - b.price); // 按书的id比较大小,用于默认排序
} public static void main(String[] args) {
Book b1 = new Book(1, "红楼梦", 130.86, new GregorianCalendar(2009,
01, 25), "曹雪芹、高鄂");
Book b2 = new Book(2, "三国演义", 99.99, new GregorianCalendar(2008, 7,
8), "罗贯中 ");
Book b3 = new Book(3, "水浒传", 80.3, new GregorianCalendar(2009, 6,
28), "施耐庵 ");
Book b4 = new Book(4, "西游记", 111.8, new GregorianCalendar(2011, 6,
8), "吴承恩");
Book b5 = new Book(5, "天龙八部", 8.4, new GregorianCalendar(2011, 9,
23), "金庸");
TreeMap<Object,Integer> tm = new TreeMap<Object, Integer>();
tm.put(b1, new Integer(2550));
tm.put(b2, new Integer(1220));
tm.put(b3, new Integer(324));
tm.put(b4, new Integer(453));
tm.put(b5, new Integer(40));
Iterator it = tm.keySet().iterator();
Object key = null, value = null;
Book bb = null;
while (it.hasNext()) {
key = it.next();
bb = (Book) key;
value = tm.get(key);
System.out.println(bb.toString() + "\t库存:" + tm.get(key));
} } }

代码讲解:

创建了一个book实体类,继承了Comparable,此时它将可以进行排序。

public class Book implements Comparable {
.
.
. }

 创建一个带参构造器,用于排序使用。

 public Book(int id, String name, double price, GregorianCalendar calender,
String author) {
this.id = id;
this.name = name;
this.price = price;
this.calendar = calender;
this.author = author;
}

重写继承自父类Object的方法,满足Book类信息描述的要求 .

public String toString() {
String showStr = id + "\t" + name; // 定义显示类信息的字符串
DecimalFormat formatPrice = new DecimalFormat("0.00");// 格式化价格到小数点后两位
showStr += "\t" + formatPrice.format(price);// 格式化价格
showStr += "\t" + author;
SimpleDateFormat formatDate = new SimpleDateFormat("yyyy年MM月dd日");
showStr += "\t" + formatDate.format(calendar.getTime()); // 格式化时间
return showStr; // 返回类信息字符串
}

Comparable接口中的方法,按书的价格asc排序(排序呢方式可以自定义)

 public int compareTo(Object obj) {// Comparable接口中的方法
Book b = (Book) obj;
return (int) (this.price - b.price); // 按书的价格比较大小,用于默认排序
}

在main函数中使用treeMap对其默认排序。

 Book b1 = new Book(10000, "红楼梦", 150.86, new GregorianCalendar(2009,
01, 25), "曹雪芹、高鄂");
Book b2 = new Book(10001, "三国演义", 99.68, new GregorianCalendar(2008, 7,
8), "罗贯中 ");
Book b3 = new Book(10002, "水浒传", 100.8, new GregorianCalendar(2009, 6,
28), "施耐庵 ");
Book b4 = new Book(10003, "西游记", 120.8, new GregorianCalendar(2011, 6,
8), "吴承恩");
Book b5 = new Book(10004, "天龙八部", 10.4, new GregorianCalendar(2011, 9,
23), "搜狐");
TreeMap<Object,Integer> tm = new TreeMap<Object, Integer>();
tm.put(b1, new Integer(255));
tm.put(b2, new Integer(122));
tm.put(b3, new Integer(688));
tm.put(b4, new Integer(453));
tm.put(b5, new Integer(40));
Iterator it = tm.keySet().iterator();
Object key = null, value = null;
Book bb = null;
while (it.hasNext()) {
key = it.next();
bb = (Book) key;
value = tm.get(key);
System.out.println(bb.toString() + "\t库存:" + tm.get(key));
}

最终运行得到结果为:

10004	天龙八部	10.40	搜狐	2011年10月23日	库存:40
10001 三国演义 99.68 罗贯中 2008年08月08日 库存:122
10002 水浒传 100.80 施耐庵 2009年07月28日 库存:688
10003 西游记 120.80 吴承恩 2011年07月08日 库存:453
10000 红楼梦 150.86 曹雪芹、高鄂 2009年02月25日 库存:255

Comparator测试代码:

/**
*
* UseComparator
* @author LongJin
* 2017年2月28日 下午2:26:45
*/
public class UseComparator {
public static void main(String args[]) {
List<Book> list = new ArrayList<Book>(); // 数组序列
Book b1 = new Book(10000, "红楼梦", 150.86, new GregorianCalendar(2009,
01, 25), "曹雪芹、高鄂");
Book b2 = new Book(10001, "三国演义", 99.68, new GregorianCalendar(2008, 7,
8), "罗贯中 ");
Book b3 = new Book(10002, "水浒传", 100.8, new GregorianCalendar(2009, 6,
28), "施耐庵 ");
Book b4 = new Book(10003, "西游记", 120.8, new GregorianCalendar(2011, 6,
8), "吴承恩");
Book b5 = new Book(10004, "天龙八部", 10.4, new GregorianCalendar(2011, 9,
23), "搜狐");
list.add(b1);
list.add(b2);
list.add(b3);
list.add(b4);
list.add(b5);
// Collections.sort(list); //没有默认比较器,不能排序
System.out.println("数组序列中的元素:");
myprint(list);
Collections.sort(list, new PriceComparator()); // 根据价格排序
System.out.println("按书的价格排序:");
myprint(list);
Collections.sort(list, new CalendarComparator()); // 根据时间排序
System.out.println("按书的出版时间排序:");
myprint(list);
} // 自定义方法:分行打印输出list中的元素
public static void myprint(List<Book> list) {
Iterator it = list.iterator(); // 得到迭代器,用于遍历list中的所有元素
while (it.hasNext()) {// 如果迭代器中有元素,则返回true
System.out.println("\t" + it.next());// 显示该元素
}
} // 自定义比较器:按书的价格排序 升序
static class PriceComparator implements Comparator<Book> {
@Override
public int compare(Book o1, Book o2) {
// TODO Auto-generated method stub
return new Double(o1.price).compareTo(new Double(o2.price));
} } // 自定义比较器:按书出版时间来排序 降序
static class CalendarComparator implements Comparator<Book> {
@Override
public int compare(Book o1, Book o2) {
// TODO Auto-generated method stub
return o2.calendar.compareTo(o1.calendar);
}
}
}

自定义比较器按书的价格升序:

  // 自定义比较器:按书的价格排序
static class PriceComparator implements Comparator<Book> {
@Override
public int compare(Book o1, Book o2) {
// TODO Auto-generated method stub
return new Double(o1.price).compareTo(new Double(o2.price));
} }

自定义比较器按出版时间降序:

// 自定义比较器:按书出版时间来排序
static class CalendarComparator implements Comparator<Book> {
@Override
public int compare(Book o1, Book o2) {
// TODO Auto-generated method stub
return o2.calendar.compareTo(o1.calendar);
}
}

在main中添加book的list数组:

List<Book> list = new ArrayList<Book>(); // 数组序列
Book b1 = new Book(10000, "红楼梦", 150.86, new GregorianCalendar(2009,
01, 25), "曹雪芹、高鄂");
Book b2 = new Book(10001, "三国演义", 99.68, new GregorianCalendar(2008, 7,
8), "罗贯中 ");
Book b3 = new Book(10002, "水浒传", 100.8, new GregorianCalendar(2009, 6,
28), "施耐庵 ");
Book b4 = new Book(10003, "西游记", 120.8, new GregorianCalendar(2011, 6,
8), "吴承恩");
Book b5 = new Book(10004, "天龙八部", 10.4, new GregorianCalendar(2011, 9,
23), "搜狐");
// 添加对象到ArrayList中
list.add(b1);
list.add(b2);
list.add(b3);
list.add(b4);
list.add(b5);

自定义方法:分行打印输出list中的元素  :

  public static void myprint(List<Book> list) {
Iterator it = list.iterator(); // 得到迭代器,用于遍历list中的所有元素
while (it.hasNext()) {// 如果迭代器中有元素,则返回true
System.out.println("\t" + it.next());// 显示该元素
}
}

最后将list数组以不同的比较器进行排序:

//Collections.sort(list); //没有默认比较器,不能排序  但这里的BOok类实现了Comparable接口,所以会以默认比较器排序
System.out.println("数组序列中的元素:");
myprint(list);
Collections.sort(list, new PriceComparator()); // 根据价格排序
System.out.println("按书的价格排序:");
myprint(list);
Collections.sort(list, new CalendarComparator()); // 根据时间排序
System.out.println("按书的出版时间排序:");
myprint(list);

运行代码,得出结果:

数组序列中的元素:
10000 红楼梦 150.86 曹雪芹、高鄂 2009年02月25日
10001 三国演义 99.68 罗贯中 2008年08月08日
10002 水浒传 100.80 施耐庵 2009年07月28日
10003 西游记 120.80 吴承恩 2011年07月08日
10004 天龙八部 10.40 搜狐 2011年10月23日
按书的价格排序:
10004 天龙八部 10.40 搜狐 2011年10月23日
10001 三国演义 99.68 罗贯中 2008年08月08日
10002 水浒传 100.80 施耐庵 2009年07月28日
10003 西游记 120.80 吴承恩 2011年07月08日
10000 红楼梦 150.86 曹雪芹、高鄂 2009年02月25日
按书的出版时间排序:
10004 天龙八部 10.40 搜狐 2011年10月23日
10003 西游记 120.80 吴承恩 2011年07月08日
10002 水浒传 100.80 施耐庵 2009年07月28日
10000 红楼梦 150.86 曹雪芹、高鄂 2009年02月25日
10001 三国演义 99.68 罗贯中 2008年08月08日