在 物件相等性 討論過如何重新定義equals()方法,如果定義類別時使用了泛型,則有幾個地方要注意的,例如:
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>) {
if(o instanceof Basket<T>) {
在 程式中instanceof對Basket<T>的型態判斷是不合法的,因為Java的泛型所採用的是型態抹除,也就是說,程式中泛型語法的 型態指定,僅提供編譯器使用,執行時期無法獲型態資訊,因而instanceof在執行時期比對時,僅能針對Basket型態比對,無法針對當中的泛型實 際型態進行比對。
如果想要通過編譯,可以使用型態通配字元?:
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是不是相同了:
public class Main {
    public static void main(String[] args) {
        Basket<Integer> b1 = new Basket<Integer>(1, 2);
        Basket<Integer> b2 = new Basket<Integer>(1, 2);
        Basket<Integer> b3 = new Basket<Integer>(2, 2);
        Basket<String> b4 = new Basket<String>("1", "2");
        System.out.println(b1.equals(b2));       // true
        System.out.println(b1.equals(b3));       // false
        System.out.println(b1.equals(b4));       // false
    }
}看起來不錯,不過來看看下面這個例子:
public class Main {
    public static void main(String[] args) {
        Basket<String> b1 = new Basket<String>();
        Basket<Integer> b2 = new Basket<Integer>();
        System.out.println(b1.equals(b2));    // true
    }
}Basket<Integer>與Basket<String>若是視作不同的型態,則b1與b2 應視為不相等,實際上,由於Java採用型態抹除的方式,結果就是認為在這種情況下,b1與b2是相等的。其實這也可以在以下的例子中看到:
public class Main {
    public static void main(String[] args) {
        List<Integer> l1 = new ArrayList<Integer>();
        List<String> l2 = new ArrayList<String>();
        System.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();
    }
}
