物件相等性(下) by caterpillar | CodeData
top

物件相等性(下)

分享:

物件相等性 (上) << 前情

如果定義類別時使用了泛型,在定義物件相等性時,則有幾個地方要注意的,例如:

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: 長角的東西怎麼比

分享:
按讚!加入 CodeData Facebook 粉絲群

留言

留言請先。還沒帳號註冊也可以使用FacebookGoogle+登錄留言

熱門論壇文章

熱門技術文章