1. 程式人生 > 其它 >集合容器中Strategy設計模式之Comparable&Comparator介面

集合容器中Strategy設計模式之Comparable&Comparator介面

目錄

1 集合容器中Strategy設計模式

前面我們說TreeMapTreeSet都是有順序的集合,而順序的維持是要靠一個比較器Comparator或者mapkey實現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的一個屬性,具體要用什麼樣的比較大小邏輯,呼叫方法,當然不傳的話,自己也有一個預設的策略,這樣就不怕不傳了

講到這裡ComparableComparator就講完了,但是好像有個概念我們還沒有說,那就是什麼是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中ComparatorComparable是怎麼定義

 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方法,用於比較是否相等