前面我们说TreeMap
和TreeSet
都是有顺序的集合,而顺序的维持是要靠一个比较器Comparator
或者map
的key
实现Comparable
接口
既然说到排序,首先我们不用去关心什么是Strategy
设计模式,也不用关心它为了解决什么问题而存在,我们直接从排序开始看。
假设有一个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排序,但是要是对其他对象排序就不行了,最好有个统一通用方法
先明确下目标,我们要实现的仍然是排序,但是我们不去进行大小比较,比较大小的功能由具体的类自己负责
首先我们定义一个接口,提供一个标准给要进行排序的类:
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类
,不想用收入作为比较了,想按照年龄进行比较,或者按照年龄+收入的组合进行比较,目前就不好解决了
那么问题来了,想一下,能不能进一步的封装,既然不能去改变一些类的代码,那么能不能将比较大小的逻辑拿出来呢?既然需求总是变,那么能不能把需求也进行抽象,需求细节自己实现
我们要将比较大小的逻辑拿出来,首先还是要定义一个标准,要使用进行排序,就得按照规矩来。
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
设计模式
Strategy
设计模式中文叫做策略设计模式
,其实我们就算不知道什么是策略模式不是也将上面的问题搞定了,所以啊,不要太在意于概念的东西,首先你要会用,能解决。
不过还是得来解释下策略模式的概念:策略模式是针对一组算法,将每个算法封装到具有共同接口的独立的类中,使得他们可以互相的替换,而客户端在调用的时候能够互不影响。
策略模式通常有这么几个角色:
PersonAgeComparator
类策略模式的优缺点是什么:
优点:(1)将具体算法逻辑与客户类分离,
(2)避免了大量的if else判断
缺点:(1)每个算法一个类,产生了太多的类,
(2)客户端要知道所有的策略类,以便决定使用哪一个。
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
方法,用于比较是否相等