在路上

 找回密码
 立即注册
在路上 站点首页 学习 查看内容

给jdk写注释系列之jdk1.6容器(9)-Strategy设计模式之Comparable&Comparator接口

2017-2-7 13:41| 发布者: zhangjf| 查看: 473| 评论: 0

摘要: 排序类: 1 public class DataSort { 2 3 public static void sort( int arr) { 4 for (int i = arr.length; i 0; i--) { 5 for (int j = 0; j i - 1; j++) { 6 ...

排序类:

  1. 1 public class DataSort {
  2. 2
  3. 3 public static void sort( int[] arr) {
  4. 4 for (int i = arr.length; i > 0; i--) {
  5. 5 for (int j = 0; j < i - 1; j++) {
  6. 6 // 如果前一个比后一个大,那么就把大值交换到后面去
  7. 7 if (arr[j] > arr[j + 1]) {
  8. 8 int temp = arr[j];
  9. 9 arr[j] = arr[j + 1];
  10. 10 arr[j + 1] = temp;
  11. 11 }
  12. 12 }
  13. 13 }
  14. 14 }
  15. 15 }
复制代码

测试类:

  1. 1 public class Test {
  2. 2
  3. 3 public static void main(String[] args) {
  4. 4 int[] arr = new int[] { 9, 5, 2, 7 };
  5. 5 DataSort. sort(arr);
  6. 6 for (int i : arr) {
  7. 7 System. out.print(i + " " );
  8. 8 }
  9. 9 }
  10. 10 }
复制代码

运行一下看看结果:

ok,我们已经完成排序了,但是,我不仅要去对int进行排序,还要对其他的事物进行排序,比如说人,那怎么做呢?

首先我们需要先定义一个Penson类,有什么属性呢,简单一点就有姓名,年龄和收入,定义一下:

  1. 1 public class Person {
  2. 2
  3. 3 private String name ;
  4. 4 private int age;
  5. 5 private int money;
  6. 6
  7. 7 public Person(String name, int age, int money) {
  8. 8 this.name = name;
  9. 9 this.age = age;
  10. 10 this.money = money;
  11. 11 }
  12. 12
  13. 13 public String getName() {
  14. 14 return name ;
  15. 15 }
  16. 16
  17. 17 public void setName(String name) {
  18. 18 this.name = name;
  19. 19 }
  20. 20
  21. 21 public int getAge() {
  22. 22 return age ;
  23. 23 }
  24. 24
  25. 25 public void setAge(int age) {
  26. 26 this.age = age;
  27. 27 }
  28. 28
  29. 29 public int getMoney() {
  30. 30 return money ;
  31. 31 }
  32. 32
  33. 33 public void setMoney(int money) {
  34. 34 this.money = money;
  35. 35 }
  36. 36
  37. 37 @Override
  38. 38 public String toString() {
  39. 39 return "Person [name=" + name + ", age=" + age + ", money=" + money
  40. 40 + "]";
  41. 41 }
  42. 42
  43. 43 }
  44. 44
复制代码

Penson这个类定义完成了,怎么进行排序呢,比如你说谁收入高谁老大,ok那么我们就按收入写一下排序方法:

  1. 1 public class DataSort {
  2. 2
  3. 3 public static void sort( int[] arr) {
  4. 4 for (int i = arr.length; i > 0; i--) {
  5. 5 for (int j = 0; j < i - 1; j++) {
  6. 6 // 如果前一个比后一个大,那么就把大值交换到后面去
  7. 7 if (arr[j] > arr[j + 1]) {
  8. 8 int temp = arr[j];
  9. 9 arr[j] = arr[j + 1];
  10. 10 arr[j + 1] = temp;
  11. 11 }
  12. 12 }
  13. 13 }
  14. 14 }
  15. 15
  16. 16 public static void sort(Person[] arr) {
  17. 17 for (int i = arr.length; i > 0; i--) {
  18. 18 for (int j = 0; j < i - 1; j++) {
  19. 19 // 如果前一个比后一个大,那么就把大值交换到后面去
  20. 20 if (arr[j].getMoney() > arr[j + 1].getMoney()) {
  21. 21 Person temp = arr[j];
  22. 22 arr[j] = arr[j + 1];
  23. 23 arr[j + 1] = temp;
  24. 24 }
  25. 25 }
  26. 26 }
  27. 27 }
  28. 28 }
复制代码

我们在DataSort中重写了一个sort(Person[] arr)方法,用来给Person类进行排序,测试一下吧:

  1. 1 public class Test {
  2. 2
  3. 3 public static void main(String[] args) {
  4. 4 // int[] arr = new int[] { 9, 5, 2, 7 };
  5. 5 // DataSort.sort(arr);
  6. 6 // for (int i : arr) {
  7. 7 // System.out.print(i + " ");
  8. 8 // }
  9. 9
  10. 10 Person p1 = new Person("张三" , 25, 100); // 张三,25岁,年薪100w
  11. 11 Person p2 = new Person("李四" , 30, 10); // 李四,30岁,年薪10w
  12. 12 Person p3 = new Person("王五" , 20, 1000); // 王五,25岁,年薪1000w
  13. 13 Person[] arr = new Person[] { p1, p2, p3 };
  14. 14
  15. 15 DataSort. sort(arr);
  16. 16 for (Person p : arr) {
  17. 17 System. out.println(p + " " );
  18. 18 }
  19. 19 }
  20. 20 }
复制代码

看下结果:

  1. Person [name=李四, age=30, money=10]
  2. Person [name=张三, age=25, money=100]
  3. Person [name=王五, age=20, money=1000]
复制代码

结果正确对不对,是不是感觉自己so牛x,我写的排序类,既可以排序整数int,又可以排序自定义的Person类,是不是有点飘飘然了。

等等,这有一盆冷水,我还要求可以对阿猫阿狗进行排序,你说再重写一个sort方法呗,那我还要求对电脑手机进行排序,对花花草草进行排序。。。现在是不是很苦恼,你一定在想,我要写一种万能的排序方法可以对任何东西进行排序。这个时候你没有疯而是进入设计的大门了,此时什么多态、封装,继承等等概念扑面而来,可惜的是你还是写不出万能的排序方法。能不能换一种思路,我们来提供一个标准,一个方法论,只提供排序的算法,具体的怎么比较大小你自己看着办,这么做可以吗?来试一下:

2.排序的方法论

2.1 comparable

我们先明确下目标,我们要实现的任然是排序,但是我们不去进行大小比较,比较大小的功能由具体的类自己负责,这么一想好像就清晰了许多的样子。

首先我们定义一个接口,提供一个标准给要进行排序的类:

  1. 1 public interface MyComparable {
  2. 2
  3. 3 /**
  4. 4 * 返回值大于0说明当前比较的Object大,小于0说明被比较的Object大,
  5. 5 * 等于0说明两个Object相等
  6. 6 */
  7. 7 public int compareTo(Object o);
  8. 8 }
复制代码

MyComparable接口我们写好了,我们规定,只要排序就必须实现MyComparable接口,而且要重写compareTo方法,返回一个int值来告诉我谁大谁小。

DataSort的排序方法sort怎么做呢,很简单了:

  1. 1 public class DataSort {
  2. 2
  3. 3 public static void sort(MyComparable[] arr) {
  4. 4 for (int i = arr.length; i > 0; i--) {
  5. 5 for (int j = 0; j < i - 1; j++) {
  6. 6 if (arr[j].compareTo(arr[j + 1]) > 0) {
  7. 7 MyComparable temp = arr[j];
  8. 8 arr[j] = arr[j + 1];
  9. 9 arr[j + 1] = temp;
  10. 10 }
  11. 11 }
  12. 12 }
  13. 13 }
  14. 14
  15. 15 }
复制代码

是不是很简单了,只要用compareTo的返回结果就可以了,下面我们让Person实现MyComparable接口试一下:

  1. 1 public class Person implements MyComparable {
  2. 2
  3. 3 private String name ;
  4. 4 private int age;
  5. 5 private int money;
  6. 6
  7. 7 public Person(String name, int age, int money) {
  8. 8 this.name = name;
  9. 9 this.age = age;
  10. 10 this.money = money;
  11. 11 }
  12. 12
  13. 13 public String getName() {
  14. 14 return name ;
  15. 15 }
  16. 16
  17. 17 public void setName(String name) {
  18. 18 this.name = name;
  19. 19 }
  20. 20
  21. 21 public int getAge() {
  22. 22 return age ;
  23. 23 }
  24. 24
  25. 25 public void setAge(int age) {
  26. 26 this.age = age;
  27. 27 }
  28. 28
  29. 29 public int getMoney() {
  30. 30 return money ;
  31. 31 }
  32. 32
  33. 33 public void setMoney(int money) {
  34. 34 this.money = money;
  35. 35 }
  36. 36
  37. 37 @Override
  38. 38 public String toString() {
  39. 39 return "Person [name=" + name + ", age=" + age + ", money=" + money
  40. 40 + "]";
  41. 41 }
  42. 42
  43. 43 @Override
  44. 44 public int compareTo(Object o) {
  45. 45 Person p = (Person)o;
  46. 46 if (this.money > p. money) {
  47. 47 return 1;
  48. 48 } else {
  49. 49 return -1;
  50. 50 }
  51. 51 }
  52. 52
  53. 53 }
复制代码

测试一下:

  1. 1 public class Test {
  2. 2
  3. 3 public static void main(String[] args) {
  4. 4 // int[] arr = new int[] { 9, 5, 2, 7 };
  5. 5 // DataSort.sort(arr);
  6. 6 // for (int i : arr) {
  7. 7 // System.out.print(i + " ");
  8. 8 // }
  9. 9
  10. 10 Person p1 = new Person("张三" , 25, 100); // 张三,25岁,年薪100w
  11. 11 Person p2 = new Person("李四" , 30, 10); // 李四,30岁,年薪10w
  12. 12 Person p3 = new Person("王五" , 20, 1000); // 王五,25岁,年薪1000w
  13. 13 Person[] arr = new Person[] { p1, p2, p3 };
  14. 14
  15. 15 DataSort. sort(arr);
  16. 16 for (Person p : arr) {
  17. 17 System. out.println(p + " " );
  18. 18 }
  19. 19 }
  20. 20 }
复制代码

看一下结果:

  1. Person [name=李四, age=30, money=10]
  2. Person [name=张三, age=25, money=100]
  3. Person [name=王五, age=20, money=1000]
复制代码

和预期的一样对不对,也就是说明我们的排序没有问题,现在你又开始飘飘然了,我写的排序终于完美了,可以对任何类进行排序,什么阿猫阿狗你只要实现MyComparable接口,统统来吧,哈哈哈。

等等,这里还有一盆冷水,我让你对长整型Long进行排序,,Long没问题啊、只要实现。。。实现什么,是不是傻了,Long是已经存在的类,你不可能重新编译它让它实现你的MyComparable接口吧,哎,这可怎么办。。。

等等先别哭,我还有另一盆冷水,对于Person类我的想法变了,不想用收入作为比较了,我想按照年龄进行比较,也没准我某天想按照年龄+收入的组合进行比较,反正我就是这么任性,反正我就是让你现在猜不透。你的需要一天三变,我不能把代码该来该去吧,这样的话开发急了会和产品打架的,怎么办呀,这两个问题我一个不会弄。。。

2.2 comparator

那么问题来了,想一下,能不能进一步的封装,既然我不能去改变一些类的代码,那么我能不能将比较大小的逻辑拿出来呢?既然你的需要总是变,而我又预测不到,那么我能不能把你的需求也进行抽象,你得需求细节你自己实现,我提供给你逻辑框架呢?答案是肯定的,说干就干!

我们要将比较大小的逻辑拿出来,首先还是要定义一个标准,要使用我进行排序,就得安装规矩来。

  1. 1 public interface MyComparator {
  2. 2 public int compare(Object o1, Object o2);
  3. 3 }
复制代码

注意,这个接口不是让你的排序类来实现的,看看我sort怎么写:

  1. 1 public class DataSort {
  2. 2
  3. 3 public static void sort(MyComparable[] arr) {
  4. 4 for (int i = arr.length; i > 0; i--) {
  5. 5 for (int j = 0; j < i - 1; j++) {
  6. 6 if (arr[j].compareTo(arr[j + 1]) > 0) {
  7. 7 MyComparable temp = arr[j];
  8. 8 arr[j] = arr[j + 1];
  9. 9 arr[j + 1] = temp;
  10. 10 }
  11. 11 }
  12. 12 }
  13. 13 }
  14. 14
  15. 15 public static void sort(Object[] arr, MyComparator c) {
  16. 16 for (int i = arr.length; i > 0; i--) {
  17. 17 for (int j = 0; j < i - 1; j++) {
  18. 18 if (c.compare(arr[j], arr[j + 1]) > 0) {
  19. 19 Object temp = arr[j];
  20. 20 arr[j] = arr[j + 1];
  21. 21 arr[j + 1] = temp;
  22. 22 }
  23. 23 }
  24. 24 }
  25. 25 }
  26. 26
  27. 27 }
复制代码

我又重写了一个sort方法,你只要把你的比较大小逻辑提供给我,我就能给你排序了。来试一下:

首先我写一个具体的比较大小逻辑类:

  1. 1 public class PersonAgeComparator implements MyComparator {
  2. 2
  3. 3 @Override
  4. 4 public int compare(Object o1, Object o2) {
  5. 5 Person p1 = (Person) o1;
  6. 6 Person p2 = (Person) o2;
  7. 7
  8. 8 if (p1.getAge() - p2.getAge() > 0) {
  9. 9 return 1;
  10. 10 } else {
  11. 11 return -1;
  12. 12 }
  13. 13 }
  14. 14
  15. 15 }
复制代码

具体看看怎么来用:

  1. 1 public class Test {
  2. 2
  3. 3 public static void main(String[] args) {
  4. 4 // int[] arr = new int[] { 9, 5, 2, 7 };
  5. 5 // DataSort.sort(arr);
  6. 6 // for (int i : arr) {
  7. 7 // System.out.print(i + " ");
  8. 8 // }
  9. 9
  10. 10 Person p1 = new Person("张三" , 25, 100); // 张三,25岁,年薪100w
  11. 11 Person p2 = new Person("李四" , 30, 10); // 李四,30岁,年薪10w
  12. 12 Person p3 = new Person("王五" , 20, 1000); // 王五,25岁,年薪1000w
  13. 13 Person[] arr = new Person[] { p1, p2, p3 };
  14. 14
  15. 15 DataSort. sort(arr, new PersonAgeComparator());
  16. 16 for (Person p : arr) {
  17. 17 System. out.println(p + " " );
  18. 18 }
  19. 19 }
  20. 20 }
复制代码

我只需要把我的比较大小逻辑类传入sort就可以了,看下结果:

  1. Person [name=王五, age=20, money=1000]
  2. Person [name=张三, age=25, money=100]
  3. Person [name=李四, age=30, money=10]
复制代码

哇,成功了,现在你在告诉我,要比较Long类型,ok啊,写一个LongComparator就行了,,还要组合Person类的年龄和收入,那我写一个PersonAgeAndMoneyComparator就好了,这下完美了,我已经做到了足够灵活,任意的扩展,哈哈哈。。。

别着急,我还有问题(我弄死你),这次不是冷水了,放心。想一个问题,现在Person类和PersonAgeComparator类两个是独立的,它们是靠sort这个排序方法联系在一起的。但是我想让他们两个联系密切一些,我们在讲低耦合的时候也在讲高内聚,毕竟Person类和他的比较大小逻辑是紧密联系的,怎么办呢,那就是将Comparator封装成Person的一个属性。

来看一下:

  1. 1 public class Person implements MyComparable {
  2. 2
  3. 3 private String name ;
  4. 4 private int age;
  5. 5 private int money;
  6. 6
  7. 7 private MyComparator comparator = new PersonAgeComparator();
  8. 8
  9. 9 public Person(String name, int age, int money) {
  10. 10 this.name = name;
  11. 11 this.age = age;
  12. 12 this.money = money;
  13. 13 }
  14. 14
  15. 15 public Person(String name, int age, int money, MyComparator comparator) {
  16. 16 this.name = name;
  17. 17 this.age = age;
  18. 18 this.money = money;
  19. 19 this.comparator = comparator;
  20. 20 }
  21. 21
  22. 22 public String getName() {
  23. 23 return name ;
  24. 24 }
  25. 25
  26. 26 public void setName(String name) {
  27. 27 this.name = name;
  28. 28 }
  29. 29
  30. 30 public int getAge() {
  31. 31 return age ;
  32. 32 }
  33. 33
  34. 34 public void setAge(int age) {
  35. 35 this.age = age;
  36. 36 }
  37. 37
  38. 38 public int getMoney() {
  39. 39 return money ;
  40. 40 }
  41. 41
  42. 42 public void setMoney(int money) {
  43. 43 this.money = money;
  44. 44 }
  45. 45
  46. 46 @Override
  47. 47 public String toString() {
  48. 48 return "Person [name=" + name + ", age=" + age + ", money=" + money
  49. 49 + "]";
  50. 50 }
  51. 51
  52. 52 @Override
  53. 53 public int compareTo(Object o) {
  54. 54 return comparator .compare(this, o);
  55. 55 }
  56. 56
  57. 57 }
复制代码

我们将MyComparator接口封装成了Person的一个属性,具体要用什么样的比较大小逻辑,你调用方传给我,当然你不传的话,我自己也有一个默认的策略,这样我就不怕你忘记了。

讲到这里Comparable和Comparator就讲完了,但是好像有个概念我们还没有说,那就是什么是Strategy设计模式 。

3.Strategy设计模式

Strategy设计模式中文叫做策略设计模式,其实我们就算不知道什么是策略模式不是也将上面的问题搞定了么,所以啊,不要太在意于概念的东西,首先你要会用,能解决。

不过还是得来解释下策略模式的概念,大体说,不标准:策略模式是针对一组算法,将每个算法封装到具有共同接口的独立的类中,使得他们可以互相的替换,而客户端在调用的时候能够互不影响。

策略模式通常有这么几个角色:

(1) 环境(Context)角色: 持有一个Strategy的引用。——Person类

(2) 抽象策略(Strategy)角色: 这是一个抽象角色,通常由一个接口或抽象类实现。此角色给出所有的具体策略类所需的接口。——MyComparator接口

(3) 具体策略(ConcreteStrategy)角色: 包装了相关的算法或行为。——PersonAgeComparator类

策略模式的优缺点是什么:

优点:(1)将具体算法逻辑与客户类分离,(2)避免了大量的if else判断

缺点:(1)每个算法一个类,产生了太多的类,(2)客户端要知道所有的策略类,以便决定使用哪一个。

想想怎么样能有尝试解决策略模式的缺点。。。那就是工厂模式。ok这里不是主要讲设计模式,就到这里了。

4.回忆TreeMap的比较大小

  1. 1 public V put(K key, V value) {
  2. 2 ......
  3. 3 ......
  4. 4
  5. 5 // split comparator and comparable paths
  6. 6 // 当前使用的比较器
  7. 7 Comparator<? super K> cpr = comparator ;
  8. 8 // 如果比较器不为空,就是用指定的比较器来维护TreeMap的元素顺序
  9. 9 if (cpr != null) {
  10. 10 // do while循环,查找key要插入的位置(也就是新节点的父节点是谁)
  11. 11 do {
  12. 12 // 记录上次循环的节点t
  13. 13 parent = t;
  14. 14 // 比较当前节点的key和新插入的key的大小
  15. 15 cmp = cpr.compare(key, t. key);
  16. 16 // 新插入的key小的话,则以当前节点的左孩子节点为新的比较节点
  17. 17 if (cmp < 0)
  18. 18 t = t. left;
  19. 19 // 新插入的key大的话,则以当前节点的右孩子节点为新的比较节点
  20. 20 else if (cmp > 0)
  21. 21 t = t. right;
  22. 22 else
  23. 23 // 如果当前节点的key和新插入的key想的的话,则覆盖map的value,返回
  24. 24 return t.setValue(value);
  25. 25 // 只有当t为null,也就是没有要比较节点的时候,代表已经找到新节点要插入的位置
  26. 26 } while (t != null);
  27. 27 }
  28. 28 else {
  29. 29 // 如果比较器为空,则使用key作为比较器进行比较
  30. 30 // 这里要求key不能为空,并且必须实现Comparable接口
  31. 31 if (key == null)
  32. 32 throw new NullPointerException();
  33. 33 Comparable<? super K> k = (Comparable<? super K>) key;
  34. 34 // 和上面一样,喜欢查找新节点要插入的位置
  35. 35 do {
  36. 36 parent = t;
  37. 37 cmp = k.compareTo(t. key);
  38. 38 if (cmp < 0)
  39. 39 t = t. left;
  40. 40 else if (cmp > 0)
  41. 41 t = t. right;
  42. 42 else
  43. 43 return t.setValue(value);
  44. 44 } while (t != null);
  45. 45 }
  46. 46
  47. 47 ......
  48. 48 ......
  49. 49 }
复制代码

现在理解TreeMap为什么要判断有没有Comparator了吧。。如果没有的话,就用key去比较大小,但是要求key实现Comparable接口。

Strategy设计模式之Comparable&Comparator接口 完!

参见:

给jdk写注释系列之jdk1.6容器(7)-TreeMap源码解析

给jdk写注释系列之jdk1.6容器(8)-TreeSet&NavigableMap&NavigableSet源码解析

来自: http://www.cnblogs.com/tstd/p/5090401.html

最新评论

小黑屋|在路上 ( 蜀ICP备15035742号-1 

;

GMT+8, 2025-7-9 10:06

Copyright 2015-2025 djqfx

返回顶部