簡體   English   中英

來自Effective Java的第9項(等於合同):示例是否正確?

[英]Item 9 (equals contract) from Effective Java: is the example correct?

Bloch的精彩書籍“Effective Java”指出,如果equals不對稱,那么Collections contains的行為是不確定的。

在他給出的例子中(通過下面的小修改再現),布洛赫說他看到了“假”,但也可以看到真實或異常。

如果標准沒有指定contains(Object o)是否檢查e.equals(o)中每個項目的e.equals(o)o.equals(e) ,並且實現前者,則可以看到“true”。 然而, Collections Javadoc明確指出它必須是后者(這是我觀察到的)。

所以我看到的唯一可能是“假”或可能是異常(但是字符串Javadoc似乎排除了后者)。

我理解更廣泛的觀點,非對稱equals可能會導致集合之外的代碼出現問題,但我不會在他引用的例子中看到它。

我錯過了什么嗎?

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

class CIString {
  private final String s;

  public CIString(String s) {
    this.s = s;
  }

  @Override public boolean equals( Object o ) {
    System.out.println("Calling CIString.equals from " + this.s );
    if ( o instanceof CIString) 
      return s.equalsIgnoreCase( ( (CIString) o).s);
    if ( o instanceof String) 
      return s.equalsIgnoreCase( (String) o );
    return false;
  }
  // Always override hashCode when you override equals
  // This is an awful hash function (everything collides -> performance is terrible!)
  // but it is semantically sound.  See Item 10 from Effective Java for more details.
  @Override public int hashCode() { return 42; }
}

public class CIS {
  public static void main(String[] args) {
   CIString a = new CIString("Polish");
   String s = "polish";

   List<CIString> list = new ArrayList<CIString>();
   list.add(a);
   System.out.println("list contains s:" + list.contains(s));
 }
}

這是一大早,所以也許我錯過了你的問題的真正意義,這段代碼將失敗:

public class CIS 
{
    public static void main(String[] args) 
    {
        CIString a = new CIString("Polish");
        String s = "polish";

        List<String> list = new ArrayList<String>();
        list.add(s);
        System.out.println("list contains a:" + list.contains(a));
    }
}

至少你的代碼找到它並且我的代碼沒有找到它是奇怪的(從理智的角度來看,這顯然不是你的代碼是如何編寫的:-)

編輯:

public class CIS {
  public static void main(String[] args) {
   CIString a = new CIString("Polish");
   String s = "polish";

   List<CIString> list = new ArrayList<CIString>();
   list.add(a);
   System.out.println("list contains s:" + list.contains(s));

   List<String> list2 = new ArrayList<String>();
   list2.add(s);
   System.out.println("list contains a:" + list2.contains(a));
 }
}

現在代碼打印出來:

list contains s:false
Calling CIString.equals from Polish
list contains a:true

這仍然沒有意義......而且非常脆弱。 如果兩個對象像a.equals(b)那樣相等,那么它們也必須等於b.equal(a),這與你的代碼不同。

來自javadoc

它是對稱的:對於任何非空引用值x和y,當且僅當y.equals(x)返回true時,x.equals(y)才應返回true。

所以,是的,本書中的示例可能與集合API的Javadoc相反,但原則是正確的。 不應該創建一個奇怪的行為或最終會出現問題的等於方法。

編輯2:

該文的關鍵點是:

在Sun目前的實現中,它恰好返回false,但這只是一個實現工件。 在另一個實現中,它可以很容易地返回true或拋出運行時異常。 一旦違反了equals合同,您就不知道其他對象在面對對象時的行為方式。

但是,鑒於Javadoc說它看起來似乎行為是固定的而不是實現工件。

如果它不在javadoc中,或者javadoc不是規范的一部分,那么它可能會在以后更改,代碼將不再起作用。

在我現在看的書的副本(第2版)中,項目編號為8,關於對稱性要求的整個部分顯示得相當差。

您提到的特殊問題似乎是由於使用代碼與實現過於接近而造成的,這掩蓋了作者試圖提出的觀點。 我的意思是,我查看list.contains(s)並通過它看到ArrayList和String,關於返回true或拋出異常的所有推理對我來說list.contains(s)有意義,真的。

  • 我不得不將“使用代碼”從實現中移開,以了解它的實現方式:

     void test(List<CIString> list, Object s) { if (list != null && list.size() > 0) { if (list.get(0).equals(s)) { // unsymmetric equality in CIString assert !list.contains(s); // "usage code": list.contain(s) } } } 

上面看起來很奇怪但只要list是我們的ArrayList而s是我們的String,測試通過。

現在,如果我們使用其他東西而不是String,會發生什么? 比如,如果我們傳遞new CIString("polish")作為第二個參數會發生什么?

看, 盡管通過第一次equals檢查 ,斷言在下一行失敗 - 因為contains將為此對象返回true。


類似的推理適用於布洛赫提到異常的部分。 這次,我將第二個參數保留為String,但是對於第一個參數,想象一個除ArrayList之外的List實現(這是合法的不是它)。

  • 你看,List實現通常被允許從contains拋出ClassCastException,我們只需要得到一個完全相同的東西並將它用於我們的測試。 想到的一個可能是基於使用適當的比較器包裹在我們原始列表中的TreeSet。

     List<CIString> wrapperWithCce(List<CIString> original, Comparator<CIString> comparator) { final TreeSet<CIString> treeSet = new TreeSet<CIString>(comparator); treeSet.addAll(original); return new ArrayList<CIString>() { { addAll(treeSet); } @Override public boolean contains(Object o) { return treeSet.contains(o); // if o is String, will throw CCE } }; } 

如果我們傳遞上面的列表和字符串“polish”來test什么? list.get(0).equals(s)仍將通過檢查,但list.contains(s)將從TreeSet.contains()中拋出ClassCastException。

這似乎就像布洛赫在提到list.contains(s)可能會拋出異常時所list.contains(s)的情況一樣 - 盡管經過了第一次equals檢查

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM