David8579 · 程式 ·

學習廖雪峰的JAVA教程---集合(編寫equals方法)

我們知道List是一種有序鍊表:List內部按照放入元素的先後順序存放,並且每個元素都可以通過索引確定自己的位置。

List還提供了boolean contains(Object o)方法來判斷List是否包含某個指定元素。此外,int indexOf(Object o)方法可以返回某個元素的索引,如果元素不存在,就返回-1。

我們來看一個例子:

import java.util.List;

public class Main {

public static void main(String[] args) {

List<Number> list = List.of("A", "B", "C");

System.out.println(list.contains("C")); // true

System.out.println(list.contains("X")); // false

System.out.println(list.indexOf("C")); // 2

System.out.println(list.indexOf("X")); // -1

}

}

這裡我們注意一個問題,我們往List中添加的"C"和調用contains("C")傳入的"C"是不是同一個實例?

如果這兩個"C"不是同一個實例,這段代碼是否還能得到正確的結果?我們可以改寫一下代碼測試一下:

import java.util.List;public class Main { public static void main(String[] args) { List<String> list = List.of("A", "B", "C"); System.out.println(list.contains(new String("C"))); // true or false? System.out.println(list.indexOf(new String("C"))); // 2 or -1? }}

因為我們傳入的是new String("C"),所以一定是不同的實例。結果仍然符合預期,這是為什麼呢?

因為List內部並不是通過==判斷兩個元素是否相等,而是使用equals()方法判斷兩個元素是否相等,例如contains()方法可以實現如下:

public class ArrayList { Object[] elementData; public boolean contains(Object o) { for (int i = 0; i < size; i++) { if (o.equals(elementData[i])) { return true; } } return false; }}

因此,要正確使用List的contains()、indexOf()這些方法,放入的實例必須正確覆寫equals()方法,否則,放進去的實例,查找不到。我們之所以能正常放入String、Integer這些對象,是因為Java標準庫定義的這些類已經正確實現了equals()方法。

我們以Person對象為例,測試一下:

import java.util.List;public class Main { public static void main(String[] args) { List<Person> list = List.of( new Person("Xiao Ming"), new Person("Xiao Hong"), new Person("Bob") ); System.out.println(list.contains(new Person("Bob"))); // false }}class Person { String name; public Person(String name) { this.name = name; }}

不出意外,雖然放入了new Person("Bob"),但是用另一個new Person("Bob")查詢不到,原因就是Person類沒有覆寫equals()方法(話說這裡竟然不報錯?)。

編寫equals

如何正確編寫equals()方法?equals()方法要求我們必須滿足以下條件:

  • 自反性(Reflexive):對於非null的x來說,x.equals(x)必須返回true;
  • 對稱性(Symmetric):對於非null的x和y來說,如果x.equals(y)為true,則y.equals(x)也必須為true;
  • 傳遞性(Transitive):對於非null的x、y和z來說,如果x.equals(y)為true,y.equals(z)也為true,那麼x.equals(z)也必須為true;
  • 一致性(Consistent):對於非null的x和y來說,只要x和y狀態不變,則x.equals(y)總是一致地返回true或者false;
  • 對null的比較:即x.equals(null)永遠返回false。

上述規則看上去似乎非常複雜,但其實代碼實現equals()方法是很簡單的,我們以Person類為例:

public class Person { public String name; public int age;}

首先,我們要定義「相等」的邏輯含義。對於Person類,如果name相等,並且age相等,我們就認為兩個Person實例相等。

因此,編寫equals()方法如下:

public boolean equals(Object o) { if (o instanceof Person) { Person p = (Person) o; return this.name.equals(p.name) && this.age == p.age; } return false;}

對於引用欄位比較,我們使用equals(),對於基本類型欄位的比較,我們使用==。

如果this.name為null,那麼equals()方法會報錯,因此,需要繼續改寫如下:

public boolean equals(Object o) { if (o instanceof Person) { Person p = (Person) o; boolean nameEquals = false; if (this.name == null && p.name == null) { nameEquals = true; } if (this.name != null) { nameEquals = this.name.equals(p.name); } return nameEquals && this.age == p.age; } return false;}

如果Person有好幾個引用類型的欄位,上面的寫法就太複雜了。要簡化引用類型的比較,我們使用Objects.equals()靜態方法:

public boolean equals(Object o) { if (o instanceof Person) { Person p = (Person) o; return Objects.equals(this.name, p.name) && this.age == p.age; } return false;}

因此,我們總結一下equals()方法的正確編寫方法:

  1. 先確定實例「相等」的邏輯,即哪些欄位相等,就認為實例相等;
  2. 用instanceof判斷傳入的待比較的Object是不是當前類型,如果是,繼續比較,否則,返回false;
  3. 對引用類型用Objects.equals()比較,對基本類型直接用==比較。

使用Objects.equals()比較兩個引用類型是否相等的目的是省去了判斷null的麻煩。兩個引用類型都是null時它們也是相等的。

如果不調用List的contains()、indexOf()這些方法,那麼放入的元素就不需要實現equals()方法。

練習

給Person類增加equals方法,使得調用indexOf()方法返回正常:

import java.util.List;public class Main { public static void main(String[] args) { List<Person> list = List.of( new Person("Xiao", "Ming", 18), new Person("Xiao", "Hong", 25), new Person("Bob", "Smith", 20) ); boolean exist = list.contains(new Person("Bob", "Smith", 20)); System.out.println(exist ? "測試成功!" : "測試失敗!"); }}class Person { String firstName; String lastName; int age; public Person(String firstName, String lastName, int age) { this.firstName = firstName; this.lastName = lastName; this.age = age; } public boolean equals(Object o){ if(o instanceof Person){ Person p = (Person) o; return Objects.equals(this.firstName,p.firstName) && Objects.equals(this.lastName,p.lastName) && this.age == p.age; } return false;}}

【關鍵:

  1. List內部按照放入元素的先後順序存放,並且每個元素都可以通過索引確定自己的位置
  2. List內部並不是通過==判斷兩個元素是否相等,而是使用equals()方法判斷兩個元素是否相等
  3. 對於引用欄位比較,我們使用equals(),對於基本類型欄位的比較,我們使用==
  4. 編寫equals()方法可藉助Objects.equals()判斷

聲明:文章觀點僅代表作者本人,PTTZH僅提供信息發布平台存儲空間服務。
喔!快樂的時光竟然這麼快就過⋯