1. 寫在前面
之前看了一下實現(xiàn) Iterable 接口,自己手寫了一個 ReverseList 集合類,最終可以反向遍歷這個 集合類。
然后就讓我想到了 Comparable 接口,這里看一下如何通過實現(xiàn),從而完成排序。
2. Comparable 接口實現(xiàn)
一些基本的類型,可以直接調(diào)用 Arrays.sort() / Collections.sort() 來完成,但是對于自己寫的一些類,編譯器實際上是不知道根據(jù)什么規(guī)則來對其排序的。
因此,如果自己的類需要直接進(jìn)行排序,則需要告訴編譯器如何進(jìn)行排序。
這里有兩種實現(xiàn)方式:
- 類和排序放在一起,即類實現(xiàn)
Comparable接口 - 類和排序分開,即重新編寫一個
xxxComparator來實現(xiàn)Comparator接口
直接上代碼,這里完成一個對 Student 類 的排序,分別使用兩種方法。
/**
* 實現(xiàn)了 Comparable 的 Student 類
* @author mikeshine
*/
@Data
public class Student implements Comparable<Student>{
private Integer age;
private String name;
private Double height;
public Student(Integer age, String name, Double height){
this.age = age;
this.name = name;
this.height = height;
}
/**
* 這里是需要重寫的 compareTo() 方法
* 這個方法規(guī)定比較的規(guī)則
* @param student
* @return
*/
@Override
public int compareTo(Student student) {
return (int)(this.age - student.getAge());
}
}
/**
* 實現(xiàn)了 Comparator 的 Student 比較器
* @author mikeshine
* @date 2022-01-10
*/
public class StudentComparator implements Comparator<Student> {
@Override
public int compare(Student o1, Student o2) {
return (int)(o1.getHeight()-o2.getHeight());
}
}
兩種方式在調(diào)用時候有一些區(qū)別,后者需要作為參數(shù)傳入,前者直接調(diào)用即可。
public static void main(String[] args) {
Student ming = new Student(12,"ming",168.00);
Student hong = new Student(13,"hong",167.00);
Student xing = new Student(14,"xing",165.00);
List<Student> students = new ArrayList<>(Arrays.asList(ming,hong,xing));
// 按照Student類中的規(guī)則,即按照age 的升序
Collections.sort(students);
// 按照StudentComparator 類中的規(guī)則,即按照 height 的升序
Collections.sort(students, new StudentComparator());
}
除了上述兩種調(diào)用方式之外,更為常用的用法是通過 stream().sorted()方法來調(diào)用,這種調(diào)用方式對于后者的實現(xiàn)更加便捷,通過 lambda 表達(dá)式即可實現(xiàn)。
代碼如下
public static void main(String[] args) {
Student ming = new Student(12,"ming",168.00);
Student hong = new Student(13,"hong",167.00);
Student xing = new Student(14,"xing",165.00);
List<Student> students = new ArrayList<>(Arrays.asList(ming,hong,xing));
// 通過 Student 類中的規(guī)則
List<Student> sortedStudents = students.stream().sorted(Comparator.comparing(Student::getHeight)).collect(Collectors.toList());
// 通過自己編寫的規(guī)則,height 升序
List<Student> defaultSortedStudents = students.stream().sorted((t1,t2)-> (int)(t1.getHeight()- t2.getHeight())).collect(Collectors.toList());;
}
3. 重寫 equals() & hasCode() 方法
為什么把這一部分放到這里來看呢,因為事實上 equals() 也是一種比較,不過只是來比較是否相等,并不比較大小。
3.1 為什么要重寫 equals() 方法
同上面說的重寫 comparator() 方法 一樣,你自己寫的類,也需要知道如何判斷 equals()相等與否,我們需要告訴編譯器一種標(biāo)準(zhǔn)。
一般來說,重寫 equals() 方法按照如下的步驟進(jìn)行:
- 對于
null,返回false - 對于 非本類,返回
false - 對于 自身,返回
true - 寫比較標(biāo)準(zhǔn)
/**
* 重寫 equals & hashCode 的類
* @author mikeshine
* @date 2021-01-10
*/
@Data
public class Employee {
private int id;
private String firstName;
private String lastName;
private String department;
/**
* 這里需要規(guī)定一下該類的 equals 方法的具體比較規(guī)則
* @param obj
* @return
*/
@Override
public boolean equals(Object obj) {
if(obj==null){
return false;
}
if(!(obj instanceof Employee)){
return false;
}
if(this == obj){
return true;
}
Employee employee = (Employee)obj;
return (this.getId().equals(employee.getId()));
}
}
3.2 為什么要寫 hashCode() 方法
貌似只要有 equals() 方法就行了,但是看下面一個 case
Employee em1 = new Employee();
Employee em2 = new Employee();
em1.setId(100);
em2.setId(100);
System.out.println(em1.equals(em2));
Set set = new HashSet<>();
set.add(em1);
set.add(em2);
System.out.println(set);
那么可以看到,這里的 haseSet 中,是有兩個元素的,但是這兩個元素其實是 equal 的,即一個對象。
問題原因
java 中有如下的要求:
-
equals的對象,hash值必須相同 - hash 相同的時候,
equals不必為 true。hash碰撞的 case
為什么有這樣奇怪的要求
事實上,這樣是為了快速判斷對象是否相同。
// hashMap 中插入時做的判斷
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
從上面來看,這里判斷首先判斷插入對象和已有對象的 hashCode是否相等,如果相等,則再去看 對象是否 equals;如果不想等,則直接結(jié)束判斷。
因為
equals的對象,hash值一定是相同的。
回頭看上面的例子,正是因為沒有寫 hashCod() 方法,因此兩個 Employee 對象的 hash 值不同,所以就和插入進(jìn)去了。
如何重寫 hashCode()
這里其實沒有一種標(biāo)準(zhǔn)的寫法,其核心訴求是,返回與變量相關(guān)的 獨一無二 的 hash 值。
這里看一個case。通用的寫法是,根據(jù)一些經(jīng)典的素數(shù)來搞。變量是 String等包裝類,則直接掉用其本身的 hash(),否則直接將該變量加入。
@override
public int hashCode(){
// 先定義一個 hash 值
int hash = 17;
// 根據(jù)比較變量(用在equals 中的)加入hash值
hash = hash * 31 + getId();
hash = hash * 31 + getName.hash();
return hash;
}