Compator 和 Comparable 以及策略模式
Comparable
Comparable 接口
废话不说,直接上源码,并且截取保留了一部分注释的翻译
package java.lang;
import java.util.*;
/**
* 该接口对实现它的每个类的对象强加一个整体排序。 这个排序被称为类的自然排序 ,类的compareTo方法被称为其自然比较方法 。
*
*
* Collections.sort (和Arrays.sort )可以自动对实现此接口的对象进行列表(和数组)排序。
* 实现该接口的对象,可以使用如在键sorted map或作为在元件sorted set ,而不需要指定一个comparator 。
*
* 一类C的自然顺序被说成是与equals一致当且仅当e1.compareTo(e2) == 0对每一个e1和C类e2相同的布尔值e1.equals(e2)。
* 请注意, null不是任何类的实例, e.compareTo(null)应该抛出一个NullPointerException即使e.equals(null)返回false 。
*
* @param <T> the type of objects that this object may be compared to
*
* @author Josh Bloch
* @see java.util.Comparator
* @since 1.2
*/
public interface Comparable<T> {
public int compareTo(T o);
}
重写的 compareTo 方法必须要满足类似于 equals 方法的要求(以下是 equals 方法的要求):
1、自反性:对于任何非空引用x,x.equals(x)应该返回true。
2、对称性:对于任何引用x和y,如果x.equals(y)返回true,那么y.equals(x)也应该返回true。
3、传递性:对于任何引用x、y和z,如果x.equals(y)返回true,y.equals(z)返回true,那么x.equals(z)也应该返回true。
4、一致性:如果x和y引用的对象没有发生变化,那么反复调用x.equals(y)应该返回同样的结果。
5、非空性:对于任意非空引用x,x.equals(null)应该返回false。
对应 compareTo 的规则是:
1、将此对象与指定的对象进行比较以进行排序。 返回一个负整数,零或正整数,对应该对象小于,等于或大于指定对象。
2、对称性、非空性:实现程序必须确保sgn(x.compareTo(y)) == -sgn(y.compareTo(x))
所有x
和y。
(这意味着x.compareTo(y)
必须抛出异常 y.compareTo(x)
引发异常。)
3、传递性:(x.compareTo(y)>0 && y.compareTo(z)>0)
表示x.compareTo(z)>0
。
简而言之,若一个类实现了 comparable 接口,则意味着该类支持排序。
Integer 实现
public final class Integer extends Number implements Comparable<Integer> {
public int compareTo(Integer anotherInteger) {
return compare(this.value, anotherInteger.value);
}
public static int compare(int x, int y) {
return (x < y) ? -1 : ((x == y) ? 0 : 1);
}
}
String 实现
public int compareTo(String anotherString) {
int len1 = value.length;
int len2 = anotherString.value.length;
int lim = Math.min(len1, len2);
char v1[] = value;
char v2[] = anotherString.value;
int k = 0;
while (k < lim) {
char c1 = v1[k];
char c2 = v2[k];
if (c1 != c2) {
return c1 - c2;
}
k++;
}
return len1 - len2;
}
Comparator
// A comparison function, which imposes a total ordering on some collection of objects. Comparators can be passed to a sort
// method (such as Collections.sort or Arrays.sort) to allow precise control over the sort order. Comparators can also be used
// to control the order of certain data structures (such as sorted sets or sorted maps), or to provide an ordering for
// collections of objects that don't have a natural ordering.
@FunctionalInterface
public interface Comparator<T> {
int compare(T o1, T o2);
// 还有一些默认的比较器实现
// comparingInt 实现
public static <T> Comparator<T> comparingInt(ToIntFunction<? super T> keyExtractor) {
Objects.requireNonNull(keyExtractor);
return (Comparator<T> & Serializable)
(c1, c2) -> Integer.compare(keyExtractor.applyAsInt(c1), keyExtractor.applyAsInt(c2));
}
}
// ToIntFunction 接口
@FunctionalInterface
public interface ToIntFunction<T> {
/**
* Applies this function to the given argument.
*
* @param value the function argument
* @return the function result
*/
int applyAsInt(T value);
}
我们若需要控制某个类的次序,而该类本身不支持排序(即没有实现Comparable接口),那么,我们可以新建一个该类的比较器来进行排序。这个比较器只需要实现 Comparator 即可。
Collections.sort
对于以下实体类,我们用两种方式进行排序
class UserInfo {
public int userid;
public int age;
public String name;
public UserInfo(int userid, int age, String name) {
this.userid = userid;
this.age = age;
this.name = name;
}
}
实现 Comparable 接口
实现 Comparable 接口之后,UserInfo 类就可以像 Integer 那样自然排序了。
class UserInfo implements Comparable<UserInfo> {
public int userid;
public int age;
public String name;
public UserInfo(int userid, int age, String name) {
this.userid = userid;
this.age = age;
this.name = name;
}
@Override
public int compareTo(UserInfo o) {
if (this.age - o.age == 0) {
// 二者年龄一样大
// 根据userID 比较
return this.userid - o.userid;
}
return this.age - o.age;
}
}
使用 Comparator
显然,我们使用 Comparator 来对没有实现 Comparable 接口的对象进行排序更为简便
Collections.sort(list, ((o1, o2) -> {
if (o1.age == o2.age) {
return o1.userid - o2.userid;
}
return o1.age - o2.age;
}));
策略模式
Collection.sort 的策略模式
Comparator 体现了一种策略模式(strategy design pattern),就是不改变对象自身,而用一个策略对象(strategy object)来改变它的行为。
让我们看一下 Collections.sort 源码来体会一下这种思想吧:
// 1
public static <T> void sort(List<T> list, Comparator<? super T> c) {
list.sort(c);
}
// 2 List 接口的实现
default void sort(Comparator<? super E> c) {
Object[] a = this.toArray();
Arrays.sort(a, (Comparator) c); // 3
ListIterator<E> i = this.listIterator();
for (Object e : a) { // 重新设置数组内容,this 指代 list
i.next();
i.set((E) e);
}
}
// 3 Arrays 类
public static <T> void sort(T[] a, Comparator<? super T> c) {
if (c == null) {
sort(a); // 4
} else {
if (LegacyMergeSort.userRequested)
legacyMergeSort(a, c);
else
TimSort.sort(a, 0, a.length, c, null, 0, 0); //4 或者这里
}
}
// 4
static void sort(Object[] a, int lo, int hi, Object[] work, int workBase, int workLen) {
assert a != null && lo >= 0 && lo <= hi && hi <= a.length;
int nRemaining = hi - lo;
if (nRemaining < 2)
return;
if (nRemaining < MIN_MERGE) {
int initRunLen = countRunAndMakeAscending(a, lo, hi); // 6
binarySort(a, lo, hi, lo + initRunLen);
return;
...
}
}
// 6
private static int countRunAndMakeAscending(Object[] a, int lo, int hi) {
assert lo < hi;
int runHi = lo + 1;
if (runHi == hi)
return 1;
// Find end of run, and reverse range if descending
if (((Comparable) a[runHi++]).compareTo(a[lo]) < 0) { // Descending
while (runHi < hi && ((Comparable) a[runHi]).compareTo(a[runHi - 1]) < 0) // 调用了 Compator 的 compare 方法
runHi++;
reverseRange(a, lo, runHi);
} else { // Ascending
while (runHi < hi && ((Comparable) a[runHi]).compareTo(a[runHi - 1]) >= 0)
runHi++;
}
return runHi - lo;
}
可以看到,使用 Collections.sort
对 List 进行排序的话会调用 List.sort
方法,并且将数据字段转储到数组中进行排序,进行排序最终也还是调用了 Compator 的 compare 方法。
策略模式的结构与实现
策略模式的结构
- 抽象策略(Strategy)类:定义了一个公共接口,各种不同的算法以不同的方式实现这个接口,环境角色使用这个接口调用不同的算法,一般使用接口或抽象类实现。
- 具体策略(Concrete Strategy)类:实现了抽象策略定义的接口,提供具体的算法实现。
- 环境(Context)类:持有一个策略类的引用,最终给客户端调用。
实现
策略模式应用场景
- 一个系统需要动态地在几种算法中选择一种时,可将每个算法封装到策略类中。
- 一个类定义了多种行为,并且这些行为在这个类的操作中以多个条件语句的形式出现,可将每个条件分支移入它们各自的策略类中以代替这些条件语句。
- 系统中各算法彼此完全独立,且要求对客户隐藏具体算法的实现细节时。
- 系统要求使用算法的客户不应该知道其操作的数据时,可使用策略模式来隐藏与算法相关的数据结构。
- 多个类只区别在表现行为不同,可以使用策略模式,在运行时动态选择具体要执行的行为。