![](/img/trans.png)
[英]What is a significant field for the equals contract (effective java item 8)
[英]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.