集合容器中Strategy設計模式之Comparable&Comparator介面
1 集合容器中Strategy設計模式
前面我們說TreeMap
和TreeSet
都是有順序的集合,而順序的維持是要靠一個比較器Comparator
或者map
的key
實現Comparable
介面
既然說到排序,首先我們不用去關心什麼是Strategy
設計模式,也不用關心它為了解決什麼問題而存在,我們直接從排序開始看。
1.1 排序
假設有一個int
陣列需要排序,首先要有一個int
陣列,然後需要有一個可以實現排序的方法或類,說到排序演算法可能很多人都會什麼快速、冒泡、插入。。。這裡不是講排序演算法,隨便選一種來用就好了,網上一直流傳會冒泡可以直接入職xx公司,當然是一句腹黑的玩笑話了,那麼我們就用冒泡
來試一下:
排序類:
public class DataSort { public static void sort( int[] arr) { for (int i = arr.length; i > 0; i--) { for (int j = 0; j < i - 1; j++) { // 如果前一個比後一個大,那麼就把大值交換到後面去 if (arr[j] > arr[j + 1]) { int temp = arr[j]; arr[j] = arr[j + 1]; arr[j + 1] = temp; } } } } }
測試類:
public class Test { public static void main(String[] args) { int[] arr = new int[] { 9, 5, 2, 7 }; DataSort. sort(arr); for (int i : arr) { System. out.print(i + " " ); } } } 執行一下看看結果: 2 5 7 9
已經完成排序,但是,不僅要去對int
進行排序,還要對其他的事物進行排序,比如說人,那怎麼做呢?
首先需要先定義一個Penson類
,有什麼屬性呢,簡單一點就有姓名,年齡和收入,定義一下:
public class Person {
private String name ;
private int age;
private int money;
public Person(String name, int age, int money) {
this.name = name;
this.age = age;
this.money = money;
}
public String getName() {
return name ;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age ;
}
public void setAge(int age) {
this.age = age;
}
public int getMoney() {
return money ;
}
public void setMoney(int money) {
this.money = money;
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + ", money=" + money
+ "]";
}
}
Penson
這個類定義完成了,怎麼進行排序呢,那麼我們就按收入寫一下排序方法:
public class DataSort {
public static void sort( int[] arr) {
for (int i = arr.length; i > 0; i--) {
for (int j = 0; j < i - 1; j++) {
// 如果前一個比後一個大,那麼就把大值交換到後面去
if (arr[j] > arr[j + 1]) {
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
}
public static void sort(Person[] arr) {
for (int i = arr.length; i > 0; i--) {
for (int j = 0; j < i - 1; j++) {
// 如果前一個比後一個大,那麼就把大值交換到後面去
if (arr[j].getMoney() > arr[j + 1].getMoney()) {
Person temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
}
}
在DataSort
中重寫了一個sort(Person[] arr)
方法,用來給Person類
進行排序,測試一下吧:
public class Test {
public static void main(String[] args) {
// int[] arr = new int[] { 9, 5, 2, 7 };
// DataSort.sort(arr);
// for (int i : arr) {
// System.out.print(i + " ");
// }
Person p1 = new Person("張三" , 25, 100); // 張三,25歲,年薪100w
Person p2 = new Person("李四" , 30, 10); // 李四,30歲,年薪10w
Person p3 = new Person("王五" , 20, 1000); // 王五,25歲,年薪1000w
Person[] arr = new Person[] { p1, p2, p3 };
DataSort. sort(arr);
for (Person p : arr) {
System. out.println(p + " " );
}
}
}
看下結果:
Person [name=李四, age=30, money=10]
Person [name=張三, age=25, money=100]
Person [name=王五, age=20, money=1000]
雖然可以對Person排序,但是要是對其他物件排序就不行了,最好有個統一通用方法
1.2 排序的方法論
1.2.1 comparable
先明確下目標,我們要實現的仍然是排序,但是我們不去進行大小比較,比較大小的功能由具體的類自己負責
首先我們定義一個介面,提供一個標準給要進行排序的類:
public interface MyComparable {
/** * 返回值大於0說明當前比較的Object大,小於0說明被比較的Object大,
* 等於0說明兩個Object相等
*/
public int compareTo(Object o);
}
MyComparable
介面我們寫好了,我們規定,只要排序就必須實現MyComparable
介面,而且要重寫compareTo
方法,返回一個int
值來告訴誰大誰小。
DataSort
的排序方法sort
怎麼做呢,很簡單了:
public class DataSort {
public static void sort(MyComparable[] arr) {
for (int i = arr.length; i > 0; i--) {
for (int j = 0; j < i - 1; j++) {
if (arr[j].compareTo(arr[j + 1]) > 0) {
MyComparable temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
}
}
只要用compareTo
的返回結果就可以了,下面我們讓Person
實現MyComparable
介面試一下:
public class Person implements MyComparable {
private String name ;
private int age;
private int money;
public Person(String name, int age, int money) {
this.name = name;
this.age = age;
this.money = money;
}
public String getName() {
return name ;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age ;
}
public void setAge(int age) {
this.age = age;
}
public int getMoney() {
return money ;
}
public void setMoney(int money) {
this.money = money;
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + ", money=" + money
+ "]";
}
@Override
public int compareTo(Object o) {
Person p = (Person)o;
if (this.money > p. money) {
return 1;
} else {
return -1;
}
}
}
測試一下:
public class Test {
public static void main(String[] args) {
// int[] arr = new int[] { 9, 5, 2, 7 };
// DataSort.sort(arr);
// for (int i : arr) {
// System.out.print(i + " ");
// } 9
Person p1 = new Person("張三" , 25, 100); // 張三,25歲,年薪100w
Person p2 = new Person("李四" , 30, 10); // 李四,30歲,年薪10w
Person p3 = new Person("王五" , 20, 1000); // 王五,25歲,年薪1000w
Person[] arr = new Person[] { p1, p2, p3 };
DataSort. sort(arr);
for (Person p : arr) {
System. out.println(p + " " );
}
}
}
看一下結果:
Person [name=李四, age=30, money=10]
Person [name=張三, age=25, money=100]
Person [name=王五, age=20, money=1000]
和預期的一樣,也就是說明排序沒有問題
但是假如對長整型Long
進行排序,不可能重新編譯它讓它實現你的MyComparable
介面吧
假如對於Person類
,不想用收入作為比較了,想按照年齡進行比較,或者按照年齡+收入的組合進行比較,目前就不好解決了
1.2.2 comparator
那麼問題來了,想一下,能不能進一步的封裝,既然不能去改變一些類的程式碼,那麼能不能將比較大小的邏輯拿出來呢?既然需求總是變,那麼能不能把需求也進行抽象,需求細節自己實現
我們要將比較大小的邏輯拿出來,首先還是要定義一個標準,要使用進行排序,就得按照規矩來。
public interface MyComparator {
public int compare(Object o1, Object o2);
}
注意,這個介面不是讓排序類來實現的,看看sort
怎麼寫:
public class DataSort {
public static void sort(MyComparable[] arr) {
for (int i = arr.length; i > 0; i--) {
for (int j = 0; j < i - 1; j++) {
if (arr[j].compareTo(arr[j + 1]) > 0) {
MyComparable temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
}
public static void sort(Object[] arr, MyComparator c) {
for (int i = arr.length; i > 0; i--) {
for (int j = 0; j < i - 1; j++) {
if (c.compare(arr[j], arr[j + 1]) > 0) {
Object temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
}
}
又重寫了一個sort
方法,只要把比較大小邏輯提供下,就能給你排序了。來試一下:
首先寫一個具體的比較大小邏輯類:
public class PersonAgeComparator implements MyComparator {
@Override
public int compare(Object o1, Object o2) {
Person p1 = (Person) o1;
Person p2 = (Person) o2;
if (p1.getAge() - p2.getAge() > 0) {
return 1;
} else {
return -1;
}
}
}
具體看看怎麼來用:
public class Test {
public static void main(String[] args) {
// int[] arr = new int[] { 9, 5, 2, 7 };
// DataSort.sort(arr);
// for (int i : arr) {
// System.out.print(i + " ");
// } 9
Person p1 = new Person("張三" , 25, 100); // 張三,25歲,年薪100w
Person p2 = new Person("李四" , 30, 10); // 李四,30歲,年薪10w
Person p3 = new Person("王五" , 20, 1000); // 王五,25歲,年薪1000w
Person[] arr = new Person[] { p1, p2, p3 };
DataSort. sort(arr, new PersonAgeComparator());
for (Person p : arr) {
System. out.println(p + " " );
}
}
}
只需要把比較大小邏輯類傳入sort就可以了,看下結果:
Person [name=王五, age=20, money=1000]
Person [name=張三, age=25, money=100]
Person [name=李四, age=30, money=10]
假如現在Person類
和PersonAgeComparator類
兩個是獨立的,它們是靠sort
這個排序方法聯絡在一起的。但是想讓他們兩個聯絡密切一些,我們在講低耦合的時候也在講高內聚,畢竟Person類
和它的比較大小邏輯是緊密聯絡的,怎麼辦呢,那就是將Comparator
封裝成Person
的一個屬性。
來看一下:
public class Person implements MyComparable {
private String name ;
private int age;
private int money;
private MyComparator comparator = new PersonAgeComparator();
public Person(String name, int age, int money) {
this.name = name;
this.age = age;
this.money = money;
}
public Person(String name, int age, int money, MyComparator comparator) {
this.name = name;
this.age = age;
this.money = money;
this.comparator = comparator;
}
public String getName() {
return name ;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age ;
}
public void setAge(int age) {
this.age = age;
}
public int getMoney() {
return money ;
}
public void setMoney(int money) {
this.money = money;
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + ", money=" + money
+ "]";
}
@Override
public int compareTo(Object o) {
return comparator.compare(this, o);
}
}
將MyComparator
介面封裝成了Person
的一個屬性,具體要用什麼樣的比較大小邏輯,呼叫方法,當然不傳的話,自己也有一個預設的策略,這樣就不怕不傳了
講到這裡Comparable
和Comparator
就講完了,但是好像有個概念我們還沒有說,那就是什麼是Strategy
設計模式
1.3 Strategy設計模式
Strategy
設計模式中文叫做策略設計模式
,其實我們就算不知道什麼是策略模式不是也將上面的問題搞定了,所以啊,不要太在意於概念的東西,首先你要會用,能解決。
不過還是得來解釋下策略模式的概念:策略模式是針對一組演算法,將每個演算法封裝到具有共同介面的獨立的類中,使得他們可以互相的替換,而客戶端在呼叫的時候能夠互不影響。
策略模式通常有這麼幾個角色:
- 環境(Context)角色:持有一個Strategy的引用。——Person類
- 抽象策略(Strategy)角色:這是一個抽象角色,通常由一個介面或抽象類實現。此角色給出所有的具體策略類所需的介面。——MyComparator介面
- 具體策略(ConcreteStrategy)角色:包裝了相關的演算法或行為。——
PersonAgeComparator
類
策略模式的優缺點是什麼:
優點:(1)將具體演算法邏輯與客戶類分離,
(2)避免了大量的if else判斷
缺點:(1)每個演算法一個類,產生了太多的類,
(2)客戶端要知道所有的策略類,以便決定使用哪一個。
1.4 回憶TreeMap的比較大小
public V put(K key, V value) {
......
......
// split comparator and comparable paths
// 當前使用的比較器
Comparator<? super K> cpr = comparator ;
// 如果比較器不為空,就是用指定的比較器來維護TreeMap的元素順序
if (cpr != null) {
// do while迴圈,查詢key要插入的位置(也就是新節點的父節點是誰)
do {
// 記錄上次迴圈的節點t
parent = t;
// 比較當前節點的key和新插入的key的大小
cmp = cpr.compare(key, t. key);
// 新插入的key小的話,則以當前節點的左孩子節點為新的比較節點
if (cmp < 0)
t = t. left;
// 新插入的key大的話,則以當前節點的右孩子節點為新的比較節點
else if (cmp > 0)
t = t. right;
else
// 如果當前節點的key和新插入的key想的的話,則覆蓋map的value,返回
return t.setValue(value);
// 只有當t為null,也就是沒有要比較節點的時候,代表已經找到新節點要插入的位置
} while (t != null);
}
else {
// 如果比較器為空,則使用key作為比較器進行比較
// 這裡要求key不能為空,並且必須實現Comparable介面
if (key == null)
throw new NullPointerException();
Comparable<? super K> k = (Comparable<? super K>) key;
// 和上面一樣,喜歡查詢新節點要插入的位置
do {
parent = t;
cmp = k.compareTo(t. key);
if (cmp < 0)
t = t. left;
else if (cmp > 0)
t = t. right;
else
return t.setValue(value);
} while (t != null);
}
......
......
}
現在理解TreeMap
為什麼要判斷有沒有Comparator
了吧。。如果沒有的話,就用key
去比較大小,但是要求key
實現Comparable
介面。
來看一下jdk中Comparator
和Comparable
是怎麼定義
public interface Comparator<T> {
int compare(T o1, T o2);
boolean equals(Object obj);
}
public interface Comparable<T> {
public int compareTo(T o);
}
唯一不同的是Comparator
介面中要求重寫equals
方法,用於比較是否相等