Comparable 与 Comparator 浅析

(点击上方公众号,可快速关注)

来源:朱小厮, blog.csdn.net/u013256816/article/details/50899416

今天博主在翻阅TreeMap的源码,发现其键必须是实现Comparable或者Comparator的接口时产生了一些兴趣,比如在TreeMap中的put方法分别对Comparable和Comparator接口分别进行处理。那么疑问就来了,Comparable和Comparator接口的区别是什么,Java中为什么会存在两个类似的接口?

Comparable和Comparator接口都是用来比较大小的,首先来看一下Comparable的定义:

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

Comparator的定义如下:

package java.util; public interface Comparator<T> { int compare(T o1, T o2); boolean equals(Object obj); }

Comparable对实现它的每个类的对象进行整体排序。这个接口需要类本身去实现(这句话没看懂?没关系,接下来看个例子就明白了)。若一个类实现了Comparable 接口,实现 Comparable 接口的类的对象的 List 列表 ( 或数组)可以通过 Collections.sort(或 Arrays.sort)进行排序。此外,实现 Comparable 接口的类的对象 可以用作 “有序映射 ( 如 TreeMap)” 中的键或 “有序集合 (TreeSet)” 中的元素,而不需要指定比较器。

举例(类Person1实现了Comparable接口)

package collections; public class Person1 implements Comparable<Person1> { private int age; private String name; public Person1(String name, int age) { this.name = name; this.age = age; } @Override public int compareTo(Person1 o) { return this.age-o.age; } @Override public String toString() { return name+":"+age; } }

可以看到Person1实现了Comparable接口中的compareTo方法。实现Comparable接口必须修改自身的类,即在自身类中实现接口中相应的方法。

测试代码:

Person1 person1 = new Person1("zzh",18); Person1 person2 = new Person1("jj",17); Person1 person3 = new Person1("qq",19); List<Person1> list = new ArrayList<>(); list.add(person1); list.add(person2); list.add(person3); System.out.println(list); Collections.sort(list); System.out.println(list);

输出结果:

[zzh:18, jj:17, qq:19] [jj:17, zzh:18, qq:19]

如果我们的这个类无法修改,譬如String,我们又要兑取进行排序,当然String中已经实现了Comparable接口,如果单纯的用String举例就不太形象。对类自身无法修改这就用到了Comparator这个接口(策略模式)。

public final class Person2 { private int age; private String name; public Person2(String name, int age) { this.name = name; this.age = age; } @Override public String toString() { return name+":"+age; } //getter and setter方法省略.... }

如类Person2,这个类已经固定,无法进行对其类自身的修改,也修饰词final了,你也别想继承再implements Comparable,那么此时怎么办呢?在类的外部使用Comparator的接口。如下测试代码:

Person2 p1 = new Person2("zzh",18); Person2 p2 = new Person2("jj",17); Person2 p3 = new Person2("qq",19); List<Person2> list2 = new ArrayList<Person2>(); list2.add(p1); list2.add(p2); list2.add(p3); System.out.println(list2); Collections.sort(list2,new Comparator<Person2>(){ @Override public int compare(Person2 o1, Person2 o2) { if(o1 == null || o2 == null) return 0; return o1.getAge()-o2.getAge(); } }); System.out.println(list2);

输出结果:

[zzh:18, jj:17, qq:19] [jj:17, zzh:18, qq:19]

这里(public static <T> void sort(List<T> list, Comparator<? super T> c) )采用了内部类的实现方式,实现compare方法,对类Person2的list进行排序。

再譬如博主遇到的真实案例中,需要对String进行排序,且不区分大小写,我们知道String中的排序是字典排序,譬如:A a D排序之后为A D a,这样显然不对,那么该怎么办呢?同上(下面代码中的list是一个String的List集合):

Collections.sort(list, new Comparator<String>() { @Override public int compare(String o1, String o2) { if(o1 == null || o2 == null) return 0; return o1.toUpperCase().compareTo(o2.toUpperCase()); } });

这样就可以实现不区分大小进行排序String的集合了,是不是很方便~

细心的同学可能会有疑问,明明在Comparator接口中定义了两个方法,为什么继承的时候只实现了一个方法,难道要颠覆我对Java接口常识的理解了嚒?

实际上,我们知道当一个类没有显式继承父类的时候,会有一个默认的父类,即java.lang.Object,在Object类中有一个方法即为equals方法,所以这里并不强制要求实现Comparator接口的类要实现equals方法,直接调用父类的即可,虽然你显式的实现了equals()方法 will be a better choice~

在《Effective Java》一书中,作者Joshua Bloch推荐大家在编写自定义类的时候尽可能的考虑实现一下Comparable接口,一旦实现了Comparable接口,它就可以跟许多泛型算法以及依赖于改接口的集合实现进行协作。你付出很小的努力就可以获得非常强大的功能。

事实上,Java平台类库中的所有值类都实现了Comparable接口。如果你正在编写一个值类,它具有非常明显的内在排序关系,比如按字母顺序、按数值顺序或者按年代顺序,那你就应该坚决考虑实现这个接口。

compareTo方法不但允许进行简单的等同性进行比较,而且语序执行顺序比较,除此之外,它与Object的equals方法具有相似的特征,它还是一个泛型。类实现了Comparable接口,就表明它的实例具有内在的排序关系,为实现Comparable接口的对象数组进行排序就这么简单: Arrays.sort(a);

对存储在集合中的Comparable对象进行搜索、计算极限值以及自动维护也同样简单。列如,下面的程序依赖于String实现了Comparable接口,它去掉了命令行参数列表中的重复参数,并按字母顺序打印出来:

public class WordList{ public static void main(String args[]){ Set<String> s = new TreeSet<String>(); Collections.addAll(s,args); System.out.println(s); } }

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

前者应该比较固定,和一个具体类相绑定,而后者比较灵活,它可以被用于各个需要比较功能的类使用。可以说前者属于 “静态绑定”,而后者可以 “动态绑定”。

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

转载声明:本文转载自「精讲JAVA」。

原文发布于微信公众号 - 平凡文摘(tooooooozi)

原文发表时间:2018-05-08

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏转载gongluck的CSDN博客

python笔记:#008#变量的命名

变量的命名 目标 标识符和关键字 变量的命名规则 0.1 标识符和关键字 1.1 标识符 标示符就是程序员定义的 变量名、函数名 名字 需要有 见名知义 的...

3734
来自专栏一个会写诗的程序员的博客

Java8 Lambda表达式.md什么是λ表达式λ表达式的类型λ表达式的使用其它相关概念

为了支持函数式编程,Java 8引入了Lambda表达式. 在Java 8中采用的是内部类来实现Lambda表达式.具体实现代码,可以通过debug看, 同时...

1643
来自专栏分布式系统和大数据处理

C#类型基础

本文之初的目的是讲述设计模式中的 Prototype(原型)模式,但是如果想较清楚地弄明白这个模式,需要了解对象克隆(Object Clone),Clone其实...

1003
来自专栏芋道源码1024

Java 函数式编程和 lambda 表达式

函数式编程更多时候是一种编程的思维方式,是种方法论。函数式与命令式编程的区别主要在于:函数式编程是告诉代码你要做什么,而命令式编程则是告诉代码要怎么做。说白了,...

1021
来自专栏java学习

面试题11(谈谈final、finally、finalize的区别)

考点:考察求职者对这3个java关键字的理解和区分 出现频率:★★★★ 【面试题解析】带有 final修饰符的类是不可派生的。在Java核心APⅠ中,有许多应用...

44610
来自专栏风口上的猪的文章

.NET面试题系列[12] - C# 3.0 LINQ的准备工作

"为了使LINQ能够正常工作,代码必须简化到它要求的程度。" - Jon Skeet

1133
来自专栏java学习

面试题11(谈谈final、finally、finalize的区别)

考点:考察求职者对这3个java关键字的理解和区分 出现频率:★★★★ 【面试题解析】带有 final修饰符的类是不可派生的。在Java核心APⅠ中,有许多应用...

3399
来自专栏转载gongluck的CSDN博客

python笔记:#005#算数运算符

算数运算符 计算机,顾名思义就是负责进行 数学计算 并且 存储计算结果 的电子设备 目标 算术运算符的基本使用 01. 算数运算符 算数运算符是 运算符的一种 ...

3877
来自专栏Java大联盟

Java面试手册:核心基础-2

4.abstract的method是否可同时是static,是否可同时是native,是否可同时是synchronized?

1191
来自专栏lulianqi

abstract virtaul override new 及多态

abstract 修饰符可以和类、方法、属性、索引器及事件一起使用。在类声明中使用 abstract 修饰符以指示某个类只能是其他类的基类。标记为抽象或包含在抽...

1102

扫码关注云+社区

领取腾讯云代金券