
物件相等性(下)
如果定義類別時使用了泛型,在定義物件相等性時,則有幾個地方要注意的,例如: import java.util.*; class Basket<T> { T[] things; Basket(T... things) { this.things = things; } @Override public boolean equals(Object o) { if(o instanceof Basket<T>) { // 編譯錯誤 Basket that = (Basket) o; return Arrays.deepEquals(this.things, that.things); } return false; } } 如果你編譯這個程式,會發現以下的錯誤訊息: illegal generic type for instanceof if(o instanceof Basket<T>) { 在程式中instanceof對Basket<T>的型態判斷是不合法的,因為Java的泛型所採用的是型態抹除,也就是說,程式中泛型語法的型態指定,僅提供編譯器使用,執行時期無法獲型態資訊,因而instanceof在執行時期比對時,僅能針對Basket型態比對,無法針對當中的泛型T實際型態進行比對,為此,編譯器直接拋出編譯錯誤。 如果想要通過編譯,可以使用型態通配字元?,這讓編譯器知道,你不是試圖比對泛型T: import java.util.*; class Basket<T> { T[] things; Basket(T... things) { this.things = things; } @Override public boolean equals(Object o) { if(o instanceof Basket<?>) { Basket that = (Basket) o; return Arrays.deepEquals(this.things, that.things); } return false; } } 現在你可以使用equals()來比較兩個Basket是不是相同了: import static java.lang.System.out; public class Main { public static void main(String[] args) { Basket<Integer> b1 = new Basket<>(1, 2); Basket<Integer> b2 = new Basket<>(1, 2); Basket<Integer> b3 = new Basket<>(2, 2); Basket<String> b4 = new Basket<>("1", "2"); out.println(b1.equals(b2)); // true out.println(b1.equals(b3)); // false out.println(b1.equals(b4)); // false } } 看起來不錯,不過來看看下面這個例子: import static java.lang.System.out; public class Main { public static void main(String[] args) { Basket<String> b1 = new Basket<>(); Basket<Integer> b2 = new Basket<>(); out.println(b1.equals(b2)); // true } }
Basket<Integer>與Basket<String>若是視作不同的型態,則b1與b2 應視為不相等,實際上,由於Java採用型態抹除的方式,結果就是認為在這種情況下,b1與b2是相等的。其實這也可以在以下的例子中看到: import static java.lang.System.out; public class Main { public static void main(String[] args) { List<Integer> l1 = new ArrayList<>(); List<String> l2 = new ArrayList<>(); out.println(l1.equals(l2)); // true } } List<Integer>、List<String>是不同的型態,但Java這麼想,l1、l2都是空串列,那它們不就是相等的嗎?這是採取型態抹除的結果。依此類推,Basket<Integer>與Basket<String>是不同的型態沒錯,但你的Basket定義就是比較是不是籃子(Basket<?>),以及實際籃子中放的東西是什麼,現在籃子中沒放東西,所以整個Basket的比較就會是相等的。 以下考慮繼承關係後的equals()、hashCode()定義: import java.util.*; class Basket<T> { T[] things; Basket(T... things) { this.things = things; } @Override public boolean equals(Object o) { if(o instanceof Basket<?>) { Basket that = (Basket) o; return that.canEquals(this) && Arrays.deepEquals(this.things, that.things); } return false; } public boolean canEquals(Object other) { return other instanceof Basket<?>; } @Override public int hashCode() { int sum = 1; for(T t : things) { sum = sum * 41 + t.hashCode(); } return 41 * sum + things.hashCode(); } } 轉載自:Java Essence: 長角的東西怎麼比 |