[英]Mutable objects and hashCode
有以下課程:
public class Member {
private int x;
private long y;
private double d;
public Member(int x, long y, double d) {
this.x = x;
this.y = y;
this.d = d;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + x;
result = (int) (prime * result + y);
result = (int) (prime * result + Double.doubleToLongBits(d));
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj instanceof Member) {
Member other = (Member) obj;
return other.x == x && other.y == y
&& Double.compare(d, other.d) == 0;
}
return false;
}
public static void main(String[] args) {
Set<Member> test = new HashSet<Member>();
Member b = new Member(1, 2, 3);
test.add(b);
System.out.println(b.hashCode());
b.x = 0;
System.out.println(b.hashCode());
Member first = test.iterator().next();
System.out.println(test.contains(first));
System.out.println(b.equals(first));
System.out.println(test.add(first));
}
}
它產生以下結果:
30814 29853 false true true
因為hashCode取決於對象的狀態,所以不能再正確檢索它,因此檢查包含失敗。 HashSet不再正常工作。 解決方案是使成員不可變,但這是唯一的解決方案嗎? 是否所有添加到HashSet的類都是不可變的? 有沒有其他方法來處理這種情況?
問候。
在hashsets對象要么是不可變的, 或者你需要在他們已經在一個HashSet(或HashMap的),使用后不改變他們行使紀律。
在實踐中,我很少發現這是一個問題 - 我很少發現自己需要使用復雜的對象作為鍵或設置元素,而當我這樣做時,通常不是一個問題,只是不要改變它們。 當然,如果你此時已經公開了對其他代碼的引用,那么它會變得更難。
是。 在維護類可變的同時,可以根據類的不可變值(可能是生成的id)計算hashCode和equals方法,以遵守Object類中定義的hashCode契約:
每當在執行Java應用程序期間多次在同一對象上調用它時,hashCode方法必須始終返回相同的整數,前提是不修改對象的equals比較中使用的信息。 從應用程序的一次執行到同一應用程序的另一次執行,該整數不需要保持一致。
如果兩個對象根據equals(Object)方法相等,則對兩個對象中的每一個調用hashCode方法必須生成相同的整數結果。
如果兩個對象根據equals(java.lang.Object)方法不相等,則不需要在兩個對象中的每一個上調用hashCode方法必須生成不同的整數結果。 但是,程序員應該知道為不等對象生成不同的整數結果可能會提高哈希表的性能。
根據您的情況,這可能更容易或不容易。
class Member {
private static long id = 0;
private long id = Member.id++;
// other members here...
public int hashCode() { return this.id; }
public boolean equals( Object o ) {
if( this == o ) { return true; }
if( o instanceOf Member ) { return this.id == ((Member)o).id; }
return false;
}
...
}
如果你需要一個線程安全屬性,你可以考慮使用: AtomicLong ,但同樣,它取決於你將如何使用你的對象。
如前所述,人們可以接受以下三種解決方案:
hashcode
實現上使用不可變標識並equals
檢查,例如類似ID的值。 add
/ remove
以獲取插入對象的克隆,而不是實際引用。 HashSet
不提供get
函數(例如,允許您稍后更改對象); 因此,你是安全的,不存在重復。 但是,如果出於某種原因,您確實需要在插入HashSet
后修改對象,則需要找到一種方法,通過新的更改“通知”您的Collection。 要實現此功能:
HashSet
以實現Observer
接口。 您的Member
對象必須是Observable
並在任何影響hashcode
和/或equals
setter或其他方法上update
HashSet
。 注1:擴展3,使用4:我們可以接受更改,但是那些不創建已存在對象的更改(例如,我通過分配新ID而不是將其設置為現有ID來更新用戶ID)。 否則,您必須考慮以這樣的方式轉換對象的方案,該方式現在等於Set
已存在的另一個對象。 如果您接受此限制,第4條建議將正常工作,否則您必須主動並為此類案例定義政策。
注意2:您必須在update
實現上提供更改對象的先前和當前狀態,因為您必須首先刪除舊元素(例如,在設置新值之前使用getClone()
),然后添加具有新狀態的對象。 以下代碼段只是一個示例實現,它需要根據您添加重復項的策略進行更改。
@Override
public void update(Observable newItem, Object oldItem) {
remove(oldItem);
if (add(newItem))
newItem.addObserver(this);
}
我在項目中使用了類似的技術,我需要在一個類上使用多個索引,所以我可以使用O(1)查找共享一個共同標識的對象集; 想象它作為HashSets的MultiKeymap(這非常有用,因為你可以交叉/聯合索引並且類似於類似SQL的搜索工作)。 在這種情況下,我注釋必須fireChange的方法(通常是setter) - 在發生重大更改時更新每個索引,因此索引始終使用最新狀態進行更新。
Jon Skeet列出了所有替代品。 至於為什么Map或Set中的鍵不能改變:
集合的契約意味着在任何時候,沒有兩個對象o1和o2這樣
o1 != o2 && set.contains(o1) && set.contains(o2) && o1.equals(o2)
為什么需要這一點對於Map來說尤為明顯。 從Map.get()的合同:
更正式地說,如果此映射包含從鍵
k
到值v
的映射,使得(key==null ? k==null : key.equals(k))
,則此方法返回v
,否則返回null
。 (最多可以有一個這樣的映射。)
現在,如果修改插入到地圖中的鍵,則可能使其等於已插入的其他鍵。 而且,地圖不能知道你已經這樣做了。 那么,如果你做map.get(key)
,那么地圖應該做什么,其中key
等於地圖中的幾個鍵? 沒有直觀的方法來定義這意味着什么 - 主要是因為我們對這些數據類型的直覺是集合和映射的數學理想,它們不必處理更改鍵,因為它們的鍵是數學對象,因此是不可變的。
理論上(通常也是實際上也是如此)你的課程:
hashCode
。 Set
來存儲它們是不必要的,你也可以使用List
。 放入基於散列的容器后,切勿更改'hashable field'。
好像您(會員)在黃頁(基於散列的容器)中注冊了您的電話號碼(Member.x),但您更改了號碼,那么沒有人能再在黃頁中找到您。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.