Comparable 接口
我们常常看到这样一句话
Arrays 类中的 sort 方法承诺可以对对象数组进行排序,但要求满足下列条件:对象所属的类必须实现 Comparable 接口,重写 compareTo 方法
Comparable 代码如下:
1 public interface Comparable<T> { 2 int compareTo(Object other); 3 }
比如在自己定义的 Employee 类中,对两个 Employee 实例, 想要根据各自的工资属性进行比较,则可以让 Employee 实现 Comparable 接口,并重写 compareTo 方法:
1 public int compareTo(Object otherObject){ 2 Employee other = (Employee) otherObject; 3 return Double.compare(salary, other.salary); 4 // return this.salary - other.salary; 5 }
这里,使用了静态 Double.compare 方法,如果第一个参数小于第二个参数,会返回负值;如果二者相等,会返回0;否则,返回一个正值。也可以使用注释掉的方式。
通过在 Employee 类中实现 Comparable 接口,并重写 compareTo 方法,Employee 类的实例变量就可以通过 salary 属性进行比较了。比如有一个 Employee 的数组,可以通过
1 Employee[] employees = new Employee[5];// 并添加实例 2 Arrays.sort(employees);
对 employees 数组进行升序排序,如果想要按照 salary 降序排序,只需要将 compareTo 改为
1 public int compareTo(Object otherObject){ 2 2 Employee other = (Employee) otherObject; 3 3 return Double.compare(other.salary, salary); 4 4 // return other.salary - this.salary; 5 5 }
那么如果是一个 String 类型的数组呢?如果直接调用
Arrays.sort()
则是默认按照字典序进行排序的,因为 String 类自己实现过 Compareble<String> 接口,按照字典序进行比较。现在假设我们需要按照字符串长度对字符串进行比较,总不能去更改 String 的源码吧!况且 String 也不让我们改。为了处理这种情况,Arrays.sort() 还有第二个版本,让我们自己传入一个比较器进行比较,这个比较器就是实现了 Comparator 接口的实例。
Comparator 接口
1 public interface Comparator<T> { 2 int compare(T first, T second); 3 }
如果要按照字符串长度进行比较,可以先声明一个一个实现了 Comparator 接口的比较类:
1 class LenComparator implements Comparator<String> { 2 public int compare(String first, String second) { 3 return first.length() - second.length(); 4 } 5 }
再利用 Arrays.sort 进行排序
Arrays.sort(strs, new LenComparator());
也可以写成内部类的形式,比如下面对于字符串可变数组的排序:
1 public static void sortStrings() { 2 String[] strs = new String[4]; 3 strs[0] = "dwefrwf"; 4 strs[1] = "12w"; 5 strs[2] = "w212"; 6 strs[3] = "1we2rwqw2re1e2"; 7 8 List<String> strings = new ArrayList<>(Arrays.asList(strs)); 9 10 Collections.sort(strings, new Comparator<String>() { 11 @Override 12 public int compare(String o1, String o2) { 13 return o1.length() - o2.length(); 14 } 15 }); 16 17 for (String s : strings) { 18 System.out.print(s + " "); 19 } 20 }
输出为
1 12w w212 dwefrwf 1we2rwqw2re1e2
总结
总结一点,如果对于我们自己创建的 class 类,需要对其实例进行按照某种规则比较,则应该让其类实现 Comparable 接口,并重写 compareTo 方法;如果是调用的别人的类,且不能轻易更改这个类的源码,或者想要按照新的比较方式对两个实例进行比较,就需要根据 Comparator 接口自己定义比较器了。
Map 按照 key 进行排序
之前在写程序的过程中,经常会遇到想要对映射中的键值对按照 键 或者 值 进行比较排序,如果想要按照 key 进行升序排序,Java 为我们提供了 TreeMap,它是按照 key 升序排列的有序映射。但是如果我们想要按照 key 降序排列呢,就得在声明 TreeMap 的时候为其指定比较器。比如下面一个方法,对给定数组,要统计数组中每个元素出现的次数,并且按照元素值降序排列。可以先声明一个 TreeMap 映射,为其注入按照 key 进行比较的比较器实例,这里使用的也是内部类的方式将实例注入进去,与上面的声明实例的方式有些许区别。
1 public static void orderByKey(int[] nums) { 2 Map<Integer, Integer> map = new TreeMap<>( 3 new Comparator<Integer>() { 4 public int compare(Integer obj1, Integer obj2) { 5 // 按照 key 降序排序 6 //return obj2.compareTo(obj1); 7 return obj2 - obj1; 8 // 按照 key 升序排序,TreeMap 默认的排列方式 9 // return obj1.compareTo(obj2); 10 } 11 } 12 ); 13 14 for (int i=0; i<nums.length; i++) { 15 map.put(nums[i], map.getOrDefault(nums[i], 0) + 1); 16 } 17 for (Map.Entry<Integer, Integer> entry : map.entrySet()) { 18 System.out.println(entry.getKey() + " : " + entry.getValue()); 19 } 20 }
然后测试一把
1 int[] nums = {4,1,-1,2,-1,2,3}; 2 orderByKey(nums);
输出
1 4 : 1 2 3 : 1 3 2 : 2 4 1 : 1 5 -1 : 2
可以看到所得到的映射是按照 key 降序排序输出的。
Map 按照 value 进行排序
如果要将映射里的内容按照 value 进行排序呢?该如何操作,这里不能直接在声明 Map 的时候为其注入比较器实例,因为按照 value 进行比较,map 都还没初始化,是不能获取到 key 对应的 value 值的,所以需要借助 Collectins,.sort() 方法对键值对进行 sort 排序,再将排好序的键值对放入新的 Map 里。
1 public static void orderByValue(int[] nums) { 2 Map<Integer, Integer> beforeSort = new TreeMap<>(); 3 for (int i=0; i<nums.length; i++) { 4 beforeSort.put(nums[i], beforeSort.getOrDefault(nums[i], 0) + 1); 5 } 6 7 List<Map.Entry<Integer,Integer>> list = new ArrayList<>(beforeSort.entrySet()); 8 Collections.sort(list, new Comparator<Map.Entry<Integer, Integer>>() { 9 public int compare(Map.Entry<Integer, Integer> o1, 10 Map.Entry<Integer, Integer> o2) { 11 //降序 12 // return o2.getValue().compareTo(o1.getValue()); 13 14 // 升序 15 return o1.getValue().compareTo(o2.getValue()); 16 17 } 18 }); 19 20 // 将排好序的 entry 放入有序映射中 21 Map<Integer, Integer> afterSort = new LinkedHashMap<>(); 22 for (Map.Entry<Integer, Integer> entry : list) { 23 afterSort.put(entry.getKey(), entry.getValue()); 24 } 25 26 for (Map.Entry<Integer, Integer> entry : list) { 27 System.out.println(entry.getKey() + " : " + entry.getValue()); 28 } 29 }
测试一把
1 int[] nums = {4,1,-1,2,-1,2,3}; 2 orderByValue(nums);
输出为:
1 1 : 1 2 3 : 1 3 4 : 1 4 -1 : 2 5 2 : 2