1. 程式人生 > >重寫equals方法(未完)

重寫equals方法(未完)

多次調用 是我 日常 center 書寫 了解 post 正常 並不是

  equals方法是我們日常編程中很常見的方法,Object中對這個方法的解釋如下:

boolean

equals(Object obj) 指示其他某個對象是否與此對象相等

  查看該方法的底層代碼如下:

public boolean equals(Object obj) {
        return (this == obj);
    }

通過上面的代碼很容易就能看出來,Objectequals方法上是用來比較兩個實例是否為同一個實例對象,底層還是依賴於==的判斷。

2.4.1.1 什麽情況下無需覆蓋
equals方法

Equals方法看起來是個很簡單的方法,但是重寫equals方法還是很容易出現問題的,並不是說每個類都需要來重寫equals方法。滿足下面任意條件的類都可以不用覆蓋equals方法;

  • l 類的每個實例本質上都是唯一的,如枚舉,單例等;
  • l 不關心類是否提供了邏輯相等的測試功能。Random覆蓋equals,檢查兩個Random實例是否產生相同的隨機數序列,但是這其實是很不必要的,所以從Object中繼承來的equals方法已經足夠了。
  • l 超類已經覆蓋了equals方法,從超類集成過來的行為對於子類也是合適的。List實現從ArrayList
    繼承equals實現。
  • l 類是私有的或者包訪問權限的,可以確定它的equals方法永遠不會被調用。

通過上面的敘述,知道了只要滿足四種情況,那麽無疑覆蓋equals方法,既是多余也容易出現問題,我們應該加以避免。

2.4.1.2 覆蓋equals方法

  了解了無需覆蓋equals的條件,那麽什麽條件中覆蓋equals方法呢?覆蓋equals需要註意什麽呢?

  當類具有自己特有的邏輯相等概念(不同於對象等同的概念),而且超類還沒有覆蓋equals方法以實現期望的行為時,就需要覆蓋equals方法。

  上面通常屬於“值類(value class

”的情形。值類僅僅是一個表示值的類,如IntegerDateString等。程序員在利用equals方法來比較值對象的引用時,希望知道邏輯上的值是否相等,而並非比較是否指向同一個對象。為了滿足程序員的要求,覆蓋equals是很必須的,這樣做也使得這個類的實例可以用做映射表(map)的鍵(key),或者集合(set)的元素,使映射表或map中表現出預期的行為,如不存放具有相同值。

  但是Java中有一種值類卻不在這一要求之中,那就是枚舉。在覆蓋equals方法時,必須遵循以下的通用約定:

  • l 自反性(reflexive):對於任何非null的引用值xx.equals(x)必須返回true
  • l 對稱性(symmetric):對於任何非null的引用值xy,當且僅當y.equals(x)返回true時,x.equals(y)必須返回true
  • l 傳遞性(transitive):對於任何非null的引用值xyz,如果x.equals(y)返回true,且y.equals(z)返回true,那麽x.equals(z)也必須返回true
  • l 一致性(consistent):對於任何非null的引用值xy,只有equals的比較操作在對象中所用的信息沒有被修改,多次調用x.equals(y)總會一致地返回相同的結果。
  • l 非空性(Non-nullity):對於任何非null的引用值xx.equals(null)必須返回false

  這些約定看起來很簡單,但是在實際編程中很容易違反這些約定,一旦違反這些約定,程序運行就不正常,接下來通過例子展示違反約定的情況以及如何避免這些錯誤。

自反性

  自反性:如果我們書寫的equals方法形式的功能不是用於判斷自己的對象是否等於自身(或是自身包含的值),那麽該會是多麽可怕的事情呢?如List中存放實例,然後List判斷contains()該對象時,那麽就會永遠都會返回false;通過代碼來驗證一下:

package cn.object.override.equals;
import java.util.ArrayList;
import java.util.List;
import org.junit.Test;
public class EqualsDemo {
    
    @Test
    public void test(){
        EqualsDemo demo1 = new EqualsDemo();
        EqualsDemo demo2 = new EqualsDemo();
        List<EqualsDemo> list = new ArrayList<>();
        list.add(demo1);
        System.out.println("list是否包含demo1:"+list.contains(demo1));
        System.out.println("list是否包含demo2:"+list.contains(demo2));
    }
    @Override
    public boolean equals(Object obj) {
        // TODO Auto-generated method stub
        return false;
    }
}

技術分享圖片

  我們通過ArrayList的底層代碼知道contains是通過調用當前類的indexOf方法來確定的,而indexOf是通過循環遍歷判斷參數和集合中的元素是否相等,而這個判斷是通過實例的equals方法來確定的,所以上面的兩個判斷都是false也就可以理解了,這就是equals不正確覆蓋帶來的嚴重後果了;

對稱性

  對稱性也是很重要的一個要求,並且邏輯思維也應該是這樣的,那麽如果我們不正常的覆蓋,導致情況不是這樣的會怎麽樣呢?通過一個代碼來實現;

package cn.object.override.equals;
import org.junit.Test;
/**
 * @author YorickYou
 * Code Address:https://github.com/YorickYou/JavaSE.git
 */
public class CaseInsensitiveString {
    private final String s;
    //有參構造
    public CaseInsensitiveString(String s) {
        if (s == null)
            throw new NullPointerException();
        this.s = s;
    }
    @Override
    public boolean equals(Object obj) {
        if(obj instanceof CaseInsensitiveString)
            return s.equalsIgnoreCase(((CaseInsensitiveString) obj).s);
        if (obj instanceof String) 
            return s.equalsIgnoreCase((String)obj);
        return false;
    }
    public static void main(String[] args) {
        CaseInsensitiveString string1 = new CaseInsensitiveString("abc");
        String str = "abc";
        //兩次調用equals方法,前者是調用的CaseInsensitiveString的equals方法,後者是String的equals方法
        System.out.println(string1.equals(str)+"自反性"+str.equals(string1));
    }
}

技術分享圖片

  上面對equals方法的覆蓋就嚴重違反了對稱性.如果我們往集合中添加該對象,那麽此時會出現什麽情況呢?

public static void test1(){
        List<CaseInsensitiveString> list = new ArrayList<CaseInsensitiveString>();
        CaseInsensitiveString string1 = new CaseInsensitiveString("abc");
        String str = "abc";
        list.add(string1);
        System.out.println(string1.equals(str)+"--"+list.contains(str)+"--"+list.contains(string1));
    }//true--false--true

所以在覆蓋equals方法時,我們一定要註意對稱性,對於上面問題的解決只需要將String的互操作這一段從equals中移除即可.

package cn.object.override.equals;

import java.util.ArrayList;
import java.util.List;

import org.junit.Test;

/**
 * 
 * @author YorickYou
 * Code Address:https://github.com/YorickYou/JavaSE.git
 */
public class CaseInsensitiveString {
    private final String s;
    //有參構造
    public CaseInsensitiveString(String s) {
        if (s == null)
            throw new NullPointerException();
        this.s = s;
    }    
    public static void main(String[] args) {
        CaseInsensitiveString string1 = new CaseInsensitiveString("abc");
        String str = "abc";
        //兩次調用equals方法,前者是調用的CaseInsensitiveString的equals方法,後者是String的equals方法
        System.out.println(string1.equals(str)+"自反性"+str.equals(string1));
        test1();
    }
    
    @Override
    public boolean equals(Object obj) {
        // TODO Auto-generated method stub
        return obj instanceof CaseInsensitiveString && ((CaseInsensitiveString)obj).s.equalsIgnoreCase(s);
    }
    public static void test1(){
        List<CaseInsensitiveString> list = new ArrayList<CaseInsensitiveString>();
        CaseInsensitiveString string1 = new CaseInsensitiveString("abc");
        String str = "abc";
        list.add(string1);
    System.out.println(string1.equals(str)+"--"+list.contains(str)+"--"+list.contains(string1));
    }
}

技術分享圖片

重寫equals方法(未完)