實現(xiàn) Comparable 接口完成排序

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;
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容