参考文章
场景
- 如果给你一个一维整型数组int[] array,使用Java进行升序排序怎么做?
// 直接使用sort进行排序
Arrays.sort(array)
- 降序呢?
// 自定义Comparator
Arrays.sort(array,new Comparator<Integer>() {
@Override
public int compare(int o1, int o2) {
return o2-o1;
}
});
- 还有别的方法么?答:实现Comparable接口,对于基本数据类型不太适用(麻烦)
// 可以在自定义类上实现Comparable接口,重写compareTo方法,
// 并且有必要重写了object的equals、tostring、hashcode方法,这是一个好习惯
public class Employee implements Comparable<Employee>
- 给你一个二维数组int[n][2],先按照每行的第1个元素进行降序排序,如果相同则按照第0个元素降序排列,怎么实现?
- 。。。
今天笔试遇到的一道多维数组自定义排序问题,题目本身不难,核心就在于上述第四个问题的实现,编写代码的困难也让我意识到自己对于Java自定义比较的实现理解不够深,所以决定仔细梳理一下。
java核心技术I 13.2.5 对象的比较
书中以举例子的形式说明了TreeSet的自定义类对象元素如何进行自定义排序的方法,总结如下:
Comparable
-
定义:Comparable是一个排序方法接口,通过重写实现接口的compareTo方法,实现自定义类的自定义规则比较
- 也就是说,你可以对两个对象的若干个属性进行比较,用比较的结果作为这两个对象的比较结果
- 重写完成之后,sort会自己去找到这个重写的方法,按照你规定的方式进行排序
-
适用场景:自定义类对象的集合排序
-
优点:
- 将自定义类的比较拆解成为其内部属性对应类型的逐级比较,代码编写上更为清晰直观(你想倒序的话乘以result *= -1返回即可)
-
举例子
- TreeSet <Employee>
public class Employee implements Comparable{
@Override
public int compareTo(Employee other)
{
// 这里并不是递归,而是通过自定义类Employee 的属性name调用该属性类的compareTo
// Employee compareTo ——> String compareTo
// String compareTo 相等——> Integer compare
// 从这里也能看出来String作为重要的非基本类型数据,java专门为其重写了compareTo方法,已知String的底层实现是固定长度的char数组,我们可推知String的比较终归要落到对char数组的逐位比较(字典序的实现)
// String compareTo——>
// StringLatin1.compareTo——>
// compareTo(byte[] value, byte[] other, int len1, int len2)——>
// return getChar(value, k) - getChar(other, k)
int result = this.name.compareTo(other.getName());
if (result == 0)
{
result = Integer.compare(this.getAge(), other.getAge());
}
return result;
}
private String name;
private int age;
private MyDate birthday;
// 忽略访问器的代码
··· ···
@Override
public String toString() {
return "Employee{" +
"name='" + name + '\'' +
", age=" + age +
", birthday=" + birthday +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Employee employee = (Employee) o;
if (age != employee.age) return false;
if (name != null ? !name.equals(employee.name) : employee.name != null) return false;
return birthday != null ? birthday.equals(employee.birthday) : employee.birthday == null;
}
@Override
public int hashCode() {
int result = name != null ? name.hashCode() : 0;
result = 31 * result + age;
result = 31 * result + (birthday != null ? birthday.hashCode() : 0);
return result;
}
}
- Tips:自定义类除了重写compareTo,还有必要重写toString、equals、hashCode,用以实现自定义类对象的判等(Hash类结构的存储与查询需要用到)、输出
Comparator方法接口——定制比较器
-
定义:它不是类内部实现,而是专门另外找一个实现Comparator接口的类,在这个类里面重写compare方法,将这个类的对象传递。ArrayList.sort方法就需要这样的一个定制比较器.
-
适用场景:多维基本类型数组的复杂排序(不适合在基本类型数据类的内部重写compareTo方法)
-
优点:符合开闭原则——无需实现接口,避免在类内部修改
-
举例子
- 多维基本类型数组的复杂排序——上文提到的第四个问题
// 我们向sort函数传入一个Comparator方法接口类型的对象,这是一个方法接口,
// 里面有一个可重写的compare方法(这里涉及到泛型擦除,后续深入探究一下)
Arrays.sort(test, new Comparator<int[]>() {
@Override
public int compare(int[] o1, int[] o2) {
// 如果第一个元素相等,我们就返回每个子数组第0个元素的降序排列
// 记住 return o1-o2; 是升序 return o2-o1; 是降序,就算是复杂元素的比较也会被拆解为若干个基本类型的比较
if (o1[1]==o2[1])return o2[0]-o1[0];
// 否则返回每个子数组第1个元素的降序排列
return o2[1]-o1[1];
}
});
总结:
-
都是接口,Comparable需要被某个类实现并在内部重写方法,Comparator可通过传参的方式在外部实现排序功能。
-
Comparable功能强大,可以为自定义类的自然排序乃至定制排序提供需要的排序功能,在涉及自定义类的多个属性的复杂规则的排序情况中,是更好的选择。
-
Comparator更为灵活,自已在不修改自定义类的定义的情况下进行类外的排序,符合开闭原则
-
总而言之,还是需要根据场景,仔细考量两者带来的收益与风险,选择更为合适的方法完成排序功能
网友评论