hashCode()
Bir koleksiyon için yöntemin en iyi şekilde uygulanmasına nasıl karar veririz (eşittir yöntemin doğru şekilde geçersiz kılındığı varsayılarak)?
collection.hashCode()
( hg.openjdk.java.net/jdk7/jdk7/jdk/file/9b8c96f96a0f/src/share/… )
hashCode()
Bir koleksiyon için yöntemin en iyi şekilde uygulanmasına nasıl karar veririz (eşittir yöntemin doğru şekilde geçersiz kılındığı varsayılarak)?
collection.hashCode()
( hg.openjdk.java.net/jdk7/jdk7/jdk/file/9b8c96f96a0f/src/share/… )
Yanıtlar:
En iyi uygulama? Bu zor bir sorudur çünkü kullanım şekline bağlıdır.
Bir neredeyse tüm durumlar için makul iyi uygulama içinde önerilmiştir Josh Bloch 'ın Etkili Java Item 8 (ikinci baskı) içinde. En iyisi oraya bakmaktır çünkü yazar orada yaklaşımın neden iyi olduğunu açıklar.
A oluşturun int result
ve sıfırdan farklı bir değer atayın .
İçin her alanda f
test equals()
yönteminde, bir karma kodunu hesaplamak c
tarafından:
boolean
: ise hesaplayın (f ? 0 : 1)
;byte
, char
, short
veya int
: hesapla (int)f
;long
: ise hesaplayın (int)(f ^ (f >>> 32))
;float
: ise hesaplayın Float.floatToIntBits(f)
;double
: Double.doubleToLongBits(f)
dönüş değerini her uzun değer gibi hesaplayın ve kullanın;hashCode()
Yöntemin sonucunu kullanın f == null
;Karma değerini birleştirin c
ile result
:
result = 37 * result + c
Dönüş result
Bu, çoğu kullanım durumu için karma değerlerinin uygun şekilde dağıtılmasına neden olmalıdır.
Dmeister tarafından önerilen Etkili Java uygulamasından memnunsanız, kendinizinkini almak yerine bir kütüphane çağrısı kullanabilirsiniz:
@Override
public int hashCode() {
return Objects.hashCode(this.firstName, this.lastName);
}
Bu, Guava ( com.google.common.base.Objects.hashCode
) veya Java 7 ( java.util.Objects.hash
) içindeki standart kitaplığı gerektirir , ancak aynı şekilde çalışır.
hashCode
, bir özelliğiniz varsa equals
ve bu kitaplık yöntemlerinin tam olarak ne için tasarlandığıdır. Dokümantasyon ile ilgili davranışları oldukça açıktır equals
. Bir kütüphane uygulaması, doğru bir hashCode
uygulamanın özelliklerinin ne olduğunu bilmenizi engellemediğini iddia etmez - bu kütüphaneler , geçersiz kılınan vakaların çoğuna böyle uyumlu bir uygulamayı uygulamanızı kolaylaştırırequals
.
java.util.Objects.hash(...)
olarak guava com.google.common.base.Objects.hashCode(...)
yönteminden daha JDK7 yöntemini tercih ederdim . Çoğu insanın standart kütüphaneyi ekstra bir bağımlılıktan seçeceğini düşünüyorum.
hashCode()
bir dizi için sadece onun olması gerekir java.lang.System.identityHashCode(...)
.
Eclipse tarafından sağlanan ve oldukça iyi bir iş çıkaran işlevselliği kullanmak daha iyidir ve iş mantığınızı geliştirmek için çabalarınızı ve enerjinizi koyabilirsiniz.
Bu, Android
belgelere (Wayback Machine) ve Github'daki kendi koduma bağlı olsa da, genel olarak Java için çalışacaktır. Cevabım dmeister'ın cevabının okunması ve anlaşılması çok daha kolay olan bir kod uzantısıdır .
@Override
public int hashCode() {
// Start with a non-zero constant. Prime is preferred
int result = 17;
// Include a hash for each field.
// Primatives
result = 31 * result + (booleanField ? 1 : 0); // 1 bit » 32-bit
result = 31 * result + byteField; // 8 bits » 32-bit
result = 31 * result + charField; // 16 bits » 32-bit
result = 31 * result + shortField; // 16 bits » 32-bit
result = 31 * result + intField; // 32 bits » 32-bit
result = 31 * result + (int)(longField ^ (longField >>> 32)); // 64 bits » 32-bit
result = 31 * result + Float.floatToIntBits(floatField); // 32 bits » 32-bit
long doubleFieldBits = Double.doubleToLongBits(doubleField); // 64 bits (double) » 64-bit (long) » 32-bit (int)
result = 31 * result + (int)(doubleFieldBits ^ (doubleFieldBits >>> 32));
// Objects
result = 31 * result + Arrays.hashCode(arrayField); // var bits » 32-bit
result = 31 * result + referenceField.hashCode(); // var bits » 32-bit (non-nullable)
result = 31 * result + // var bits » 32-bit (nullable)
(nullableReferenceField == null
? 0
: nullableReferenceField.hashCode());
return result;
}
DÜZENLE
Genellikle, geçersiz kıldığınızda hashcode(...)
, geçersiz kılmak da istersiniz equals(...)
. Bu yüzden yapacak veya zaten uygulayanlar için equals
, burada Github'dan iyi bir referans var ...
@Override
public boolean equals(Object o) {
// Optimization (not required).
if (this == o) {
return true;
}
// Return false if the other object has the wrong type, interface, or is null.
if (!(o instanceof MyType)) {
return false;
}
MyType lhs = (MyType) o; // lhs means "left hand side"
// Primitive fields
return booleanField == lhs.booleanField
&& byteField == lhs.byteField
&& charField == lhs.charField
&& shortField == lhs.shortField
&& intField == lhs.intField
&& longField == lhs.longField
&& floatField == lhs.floatField
&& doubleField == lhs.doubleField
// Arrays
&& Arrays.equals(arrayField, lhs.arrayField)
// Objects
&& referenceField.equals(lhs.referenceField)
&& (nullableReferenceField == null
? lhs.nullableReferenceField == null
: nullableReferenceField.equals(lhs.nullableReferenceField));
}
Öncelikle eşitlerin doğru uygulandığından emin olun. Gönderen bir IBM developerWorks makalesinde :
- Simetri: İki referans için a ve b, a. (B) eşittir ve yalnızca b. Eşittir (a) ise
- Dönüşlilik: Tüm null olmayan referanslar için, a. Eşitlikleri (a)
- Geçiş: Eğer eşittir (b) ve eşittir (c), o zaman eşittir (c)
Ardından hashCode ile ilişkilerinin kişiye (aynı makaleden) saygı duyduğundan emin olun:
- HashCode () ile tutarlılık: İki eşit nesne aynı hashCode () değerine sahip olmalıdır
Son olarak, iyi bir karma fonksiyonu ideal karma fonksiyonuna yaklaşmaya çalışmalıdır .
about8.blogspot.com, dedin ki
equals () iki nesne için true değerini döndürürse, hashCode () aynı değeri döndürmelidir. Equals () yanlış döndürürse, hashCode () farklı değerler döndürmelidir
Seninle aynı fikirde değilim. İki nesne aynı hashcode'a sahipse, eşit oldukları anlamına gelmez.
A, B'ye eşitse A.hashcode, B.hascode'a eşit olmalıdır
fakat
A.hashcode B.hascode'a eşitse, A'nın B'ye eşit olması gerektiği anlamına gelmez.
(A != B) and (A.hashcode() == B.hashcode())
buna hash fonksiyonu çarpışması diyoruz. Çünkü hash fonksiyonunun codomain'i her zaman sonludur, ancak domain genellikle değildir. Kod bölgesi ne kadar büyük olursa, çarpışma o kadar az gerçekleşmelidir. İyi karma işlevi, belirli codomain boyutu göz önüne alındığında elde edilebilecek en büyük olasılıkla farklı nesneler için farklı karma döndürmelidir. Ancak nadiren tam olarak garanti edilebilir.
Tutulma kullanıyorsanız, şunları üretebilir equals()
ve kullanabilirsiniz hashCode()
:
Kaynak -> hashCode () ve equals () oluştur.
Bu işlevi kullanarak eşitlik ve karma kod hesaplaması için hangi alanları kullanmak istediğinize karar verebilirsiniz ve Eclipse karşılık gelen yöntemleri oluşturur.
İyi bir uygulama var Etkili Java 'nın hashcode()
ve equals()
mantık Apache Commons Lang . Çıkış HashCodeBuilder ve EqualsBuilder .
Objects
sınıf Java7 üzerinde hash(Object ..args)
& equals()
yöntemleri sağlar . Bunlar jdk 1.7+ kullanan tüm uygulamalar için önerilir
IdentityHashMap
). FWIW Ben id tabanlı bir hashCode kullanın ve tüm varlıklar için eşittir.
Diğer daha ayrıntılı cevabı (kod olarak) tamamlamak için kısa bir not:
Soruyu düşünürsek nasıl-do-i-create-in-java bir karması-tablo- ve özellikle jGuru SSS girdisi , bir hash kodu değerlendirilecektir olabilir bunun üzerine diğer bazı kriterler olduğuna inanıyoruz:
Sorunuzu doğru anlarsam, özel bir toplama sınıfınız (yani Koleksiyon arayüzünden uzanan yeni bir sınıfınız) vardır ve hashCode () yöntemini uygulamak istersiniz.
Koleksiyon sınıfınız AbstractList'i genişletiyorsa, endişelenmenize gerek yoktur, zaten tüm nesneler arasında yineleme yaparak ve hashCodes () yöntemini ekleyerek çalışan equals () ve hashCode () uygulaması vardır.
public int hashCode() {
int hashCode = 1;
Iterator i = iterator();
while (i.hasNext()) {
Object obj = i.next();
hashCode = 31*hashCode + (obj==null ? 0 : obj.hashCode());
}
return hashCode;
}
İstediğiniz şey belirli bir sınıf için hash kodunu hesaplamanın en iyi yoluysa, normalde eşittir yönteminde kullandığım tüm alanları işlemek için ^ (bitwise exclusive veya) operatörünü kullanırım:
public int hashCode(){
return intMember ^ (stringField != null ? stringField.hashCode() : 0);
}
@ about8: orada oldukça ciddi bir hata var.
Zam obj1 = new Zam("foo", "bar", "baz");
Zam obj2 = new Zam("fo", "obar", "baz");
aynı karma kod
muhtemelen böyle bir şey istiyorsun
public int hashCode() {
return (getFoo().hashCode() + getBar().hashCode()).toString().hashCode();
(Bu günlerde doğrudan Java'dan hashCode'u alabilir miyim? Sanırım bazı autocasting yapıyor .. Bu durumda, toString'i atlayın, çirkin.)
foo
ve bar
yol açar hashCode
. Kişisel toString
AFAIK derlemek etmez ve bu olursa, o zaman verimsiz korkunç. Böyle bir 109 * getFoo().hashCode() + 57 * getBar().hashCode()
şey daha hızlı, daha basit ve gereksiz çarpışmalara neden olmaz.
Apache Commons EqualsBuilder ve HashCodeBuilder üzerinde yansıma yöntemlerini kullanın .
Etrafta küçük bir sarmalayıcı kullanıyorum Arrays.deepHashCode(...)
çünkü parametreler olarak verilen dizileri doğru şekilde işliyor
public static int hash(final Object... objects) {
return Arrays.deepHashCode(objects);
}
karma değerini olası aralığa eşit olarak dağıtan herhangi bir karma yöntemi iyi bir uygulamadır. (Efektif java Bkz http://books.google.com.au/books?id=ZZOiqZQIbRMC&dq=effective+java&pg=PP1&ots=UZMZ2siN25&sig=kR0n73DHJOn-D77qGj0wOxAxiZw&hl=en&sa=X&oi=book_result&resnum=1&ct=result , iyi bir ipucu yoktur) hashcode uygulaması için orada (madde 9 sanırım ...).
Burada, üst sınıf mantıkların açıklandığı bir JDK 1.7+ yaklaşımı gösterimi yer almaktadır. Object class hashCode () hesaplı, saf JDK bağımlılığı ve ekstra manuel çalışma ile oldukça rahat görüyorum. Lütfen Objects.hash()
boş toleranslı olduğunu unutmayın .
Ben herhangi bir equals()
uygulama dahil değil ama gerçekte elbette buna ihtiyacınız olacak.
import java.util.Objects;
public class Demo {
public static class A {
private final String param1;
public A(final String param1) {
this.param1 = param1;
}
@Override
public int hashCode() {
return Objects.hash(
super.hashCode(),
this.param1);
}
}
public static class B extends A {
private final String param2;
private final String param3;
public B(
final String param1,
final String param2,
final String param3) {
super(param1);
this.param2 = param2;
this.param3 = param3;
}
@Override
public final int hashCode() {
return Objects.hash(
super.hashCode(),
this.param2,
this.param3);
}
}
public static void main(String [] args) {
A a = new A("A");
B b = new B("A", "B", "C");
System.out.println("A: " + a.hashCode());
System.out.println("B: " + b.hashCode());
}
}
Standart uygulama zayıftır ve kullanılması gereksiz çarpışmalara yol açar. Bir düşünün
class ListPair {
List<Integer> first;
List<Integer> second;
ListPair(List<Integer> first, List<Integer> second) {
this.first = first;
this.second = second;
}
public int hashCode() {
return Objects.hashCode(first, second);
}
...
}
Şimdi,
new ListPair(List.of(a), List.of(b, c))
ve
new ListPair(List.of(b), List.of(a, c))
aynı hashCode
, yani 31*(a+b) + c
kullanılan çarpan List.hashCode
burada tekrar kullanılır alır. Açıkçası, çarpışmalar kaçınılmazdır, ancak gereksiz çarpışmalar üretmek sadece ... gereksizdir.
Kullanmayla ilgili oldukça akıllı bir şey yok 31
. Bilgi kaybını önlemek için çarpan garip olmalıdır (herhangi bir çarpan en azından en önemli biti kaybeder, dörtlü katlar iki kaybeder, vb.). Herhangi bir tek çarpan kullanılabilir. Küçük çarpanlar daha hızlı hesaplamaya yol açabilir (JIT, vardiya ve eklemeler kullanabilir), ancak çarpmanın modern Intel / AMD'de sadece üç döngü gecikmesi olduğu göz önüne alındığında, bu hiç de önemli değildir. Küçük çarpanlar ayrıca küçük girdiler için daha fazla çarpışmaya yol açar, bu da bazen sorun olabilir.
Asalların Z / (2 ** 32) halkasında hiçbir anlamı olmadığı için asal kullanmak anlamsızdır.
Bu nedenle, rastgele seçilmiş büyük tek bir sayı kullanmanızı tavsiye ederim (asal almaktan çekinmeyin). İ86 / amd64 CPU'ları, tek bir imzalı bayta uyan işlenenler için daha kısa bir talimat kullanabildiğinden, 109 gibi çarpanlar için küçük bir hız avantajı vardır. Çarpışmaları en aza indirmek için 0x58a54cf5 gibi bir şey alın.
Farklı yerlerde farklı çarpanlar kullanmak yararlıdır, ancak muhtemelen ek işi haklı çıkarmak için yeterli değildir.
Karma değerleri birleştirirken, genellikle boost c ++ kitaplığında kullanılan birleştirme yöntemini kullanırım:
seed ^= hasher(v) + 0x9e3779b9 + (seed<<6) + (seed>>2);
Bu, eşit bir dağıtım sağlamak için oldukça iyi bir iş çıkarır. Bu formülün nasıl çalıştığıyla ilgili bazı tartışmalar için StackOverflow yayınına bakın: boost içindeki sihirli sayı :: hash_combine
Farklı hash işlevleri hakkında iyi bir tartışma var: http://burtleburtle.net/bob/hash/doobs.html
Basit bir sınıf için, eşittir () uygulaması tarafından kontrol edilen sınıf alanlarına dayalı olarak hashCode () yöntemini uygulamak en kolay yoldur.
public class Zam {
private String foo;
private String bar;
private String somethingElse;
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
Zam otherObj = (Zam)obj;
if ((getFoo() == null && otherObj.getFoo() == null) || (getFoo() != null && getFoo().equals(otherObj.getFoo()))) {
if ((getBar() == null && otherObj. getBar() == null) || (getBar() != null && getBar().equals(otherObj. getBar()))) {
return true;
}
}
return false;
}
public int hashCode() {
return (getFoo() + getBar()).hashCode();
}
public String getFoo() {
return foo;
}
public String getBar() {
return bar;
}
}
En önemli şey hashCode () ve equals () tutarlılığını korumaktır: equals () iki nesne için true değerini döndürürse, hashCode () aynı değeri döndürmelidir. Equals () işlevi false değerini döndürürse, hashCode () yöntemi farklı değerler döndürmelidir.
("abc"+""=="ab"+"c"=="a"+"bc"==""+"abc")
. Şiddetli bir kusur. Her iki alan için hashcode'u değerlendirmek ve daha sonra bunların doğrusal kombinasyonunu hesaplamak daha iyidir (tercihen katsayılar olarak primerler kullanarak).
foo
ve bar
gereksiz bir çarpışma da üretir.
Objects.hashCode(collection)
mükemmel bir çözüm olmalı!