Soru Değişken nesneler ve hashCode


Aşağıdaki sınıfa sahip ol:

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));

}

}

Aşağıdaki sonuçları üretir:
30814 29853 false true true

HashCode nesnesinin durumuna bağlı olduğundan, artık düzgün bir şekilde alınamaz, bu nedenle denetim denetimi başarısız olur. HashSet artık düzgün çalışmıyor. Bir çözüm Üye'yi değişmez yapmak olabilir, ama tek çözüm bu mu? HashSets'e eklenen tüm sınıflar değişmez mi? Durumu halletmenin başka bir yolu var mı?

Saygılarımızla.


17
2018-01-17 21:19


Menşei


Derleyici neden karma tablolarda karma tablolara veya karma kümelerdeki nesnelere izin verilmemesine dair bir kural uygulamıyor? Bu bana korkunç geliyor. - ncmathsadist


Cevaplar:


Hashsets'teki nesneler ya değişmez olmak veya Bir hashset (veya hashmap) içinde kullanıldıktan sonra onları değiştirmemek için disiplini kullanmanız gerekir.

Pratikte bunu nadiren bir problem olarak buldum. Nadiren kendimi anahtar nesneler olarak karmaşık nesneleri kullanmam gerektiğini buluyorum ve yaptığım zaman genellikle onları değiştirmek için bir sorun değil. Tabii ki, bu zamana kadar diğer kodlara referanslar verdiyseniz, daha da zorlaşabilir.


30
2018-01-17 21:22



Değer nesneleri için. Referans nesneleri, istediğiniz gibi değiştirilebilir. - Tom Hawtin - tackline
@Tom Hawtin: Aynı zamanda referans nesnelerini değiştirmek de güvensiz olmaz mıydı? Özellikle, nesnenin karma kodunu değiştirecek veya farklı bir denklik sınıfının bir üyesi haline getirecek mutasyonlardan bahsediyorum. - Brian
@Brian: Tom'un hashCode'u veya eşitliklerini geçersiz kılmayan tiplerden söz ettiğinden şüpheleniyorum. - Jon Skeet
@Muno: "hashset'te kalırken değiştirilmemelidir" demeliydim, ancak birinin XY koordinatlarına sahip bir karma kümesi nesnesine sahip olduğunu varsayalım. Her bir karma tablonun belirli bir konum için yalnızca bir nesne tutması gerekiyorsa, bazen bir anahtar olarak konumun bulunması yararlı olabilir; bu durumda, karma kümesi, belirli bir XY koordinat çiftindeki herhangi bir nesneyi içerip içermediğini belirleyebilir. Bununla birlikte, hash tablosundaki bir nesnenin XY koordinatları hareket etmekteyse, nesneyi hash tablosundan almak artık mümkün olmayabilir. - supercat
@Muno: Bazen, bazen, bir nesne ile bilgi ilişkilendirmek için bir karma tablo kullanmak yararlı olabilir. Örneğin, bir nesne, bir şekilde kendisine bağlı olan diğer bütün nesneleri listeleyen bir karma tabloya sahip olmak isteyebilir ve bu tür bağlantıların doğasının sadece bazı XY koordinatları değiştiği için değişmesini istemeyebilir. Bu durumda, tablo, devletlerin bir yönünden ziyade, nesnelerin kimliğini (bulundukları yer gibi) kapsüllemek için kullanılacaktır. - supercat


Evet. Sınıf değişkeninizi korurken, sınıfın değişmez değerlerine dayanan hashCode ve eşittir yöntemlerini hesaplayabilirsiniz (belki de oluşturulan bir id). hash kodu Object sınıfında tanımlanan sözleşme:

  • Bir Java uygulamasının yürütülmesi sırasında aynı nesne üzerinde birden fazla kez çağrıldığında, nesne üzerindeki eşitlik karşılaştırmaları kullanıldığında hiçbir bilgi değiştirilmediyse, hashCode yöntemi tutarlı olarak aynı tamsayıyı döndürmelidir. Bu tam sayı, bir uygulamanın bir uygulamasından aynı uygulamanın başka bir uygulamasına tutarlı kalmaya ihtiyaç duymaz.

  • İki nesne eşittir (Object) yöntemine göre eşitse, iki nesnenin her birinde hashCode yöntemini çağırmak aynı tamsayı sonucunu üretmelidir.

  • İki nesnenin eşittir (java.lang.Object) yöntemine göre eşit değilse, iki nesnenin her birinde hashCode yöntemini çağırmak, farklı tamsayı sonuçları oluşturması gerekli değildir. Bununla birlikte, programcı, eşit olmayan nesneler için ayrı ayrı tamsayı sonuçları üretmenin hataların performansını artırabileceğinin farkında olmalıdır.

Durumunuza bağlı olarak bu daha kolay olabilir ya da olmayabilir.

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;
     }
     ...
 }

Bir iş parçacığı güvenli özelliğine ihtiyacınız varsa, şunları kullanmayı düşünebilirsiniz: AtomicLong bunun yerine, ama yine de, nesneyi nasıl kullanacağınıza bağlı.


7
2018-01-17 21:23



Bu yaklaşımla ilgili problem, bir HashSet'e (örneğin yeni Üye (1,2, 3)) iki farklı örnek eklersek, iki kez eklenir. Ayrıca eşittir yöntemi beklendiği gibi çalışmayacaktır. Ancak, hashCode sözleşmesini işaretlediğiniz için teşekkür ederiz: "Bir Java uygulamasının yürütülmesi sırasında aynı nesne üzerinde birden çok kez çağrıldığında, nesne üzerindeki eşitlik karşılaştırmaları için kullanılan hiçbir bilgi kullanılmadığı sürece, hashCode yöntemi tutarlı olarak aynı tamsayıyı döndürmelidir. değiştirilmiş." Onu yeterince dikkatli okumadım. - robert
Peki bu sadece bir örnek, bunu şöyle yaratabilirsiniz: public Member( int a, int b, int ) { this.id = a * 31 + b * 31 + 31 *; }  Başka bir tane yaratırsan new Member(1,2,3) aynı karmayı hesaplayacaktır fakat Bunlardan birini değiştirirseniz, senkronize etmeniz gerekir. Bir kayıt defteri / fabrika yöntemi kullanabilir ve aşağıdaki gibi nesneler oluşturabilirsiniz: Member a = Member.newInstance(1,2,3); Member b = Member.newInstance(1,2,3);  ve örneği yalnızca bir kez oluşturun. - OscarRyz
Bu yaklaşımdaki problem, temelde == operatörünün eşitlikten daha farklı olmamasıdır. Her örnek benzersiz bir uzun kimliğe sahip ve temelde aynı şeyle nesne örneği referanslarını karşılaştırarak karşılaştırıyor. İçerikte eşitliği test etmemektedir. Vakaları için içeriğin eşitliğine ihtiyaç varsa, bu çözüm yeterli olmaz - Achilles929


Jon Skeet tüm alternatifleri listeledi. Bir Harita veya Set'teki anahtarların neden değişmemesi gerektiği konusunda:

Bir Set sözleşmesi, herhangi bir zamanda, o1 ve o2 olmak üzere iki nesne olmadığını ima eder.

o1 != o2 && set.contains(o1) && set.contains(o2) && o1.equals(o2)

Bu neden gerekli bir harita için özellikle açık. Map.get () sözleşmesinden:

Daha resmi olarak, bu harita bir anahtardan bir eşleme içeriyorsa    k bir değere v öyle ki (key==null ? k==null : key.equals(k))sonra bu yöntem geri döner vaksi takdirde döner null. (Böyle bir haritalamada en fazla olabilir.)

Şimdi, bir haritaya eklenen bir anahtarı değiştirirseniz, onu zaten eklenmiş olan başka bir tuşa eşit hale getirebilirsiniz. Dahası, harita bunu yaptığınızı bilemez. Öyleyse harita ne yapmalı? map.get(key), nerede key haritadaki birkaç tuşa eşittir? Bunun ne anlama geldiğini tanımlamanın sezgisel bir yolu yoktur - esasen bu veri türleri için sezgilerimiz, anahtarlar matematiksel nesneler ve dolayısıyla değişmez olduğu için, değişen anahtarlarla uğraşmak zorunda kalmayan setlerin ve haritaların matematiksel idealidir.


3
2018-01-17 22:38





Daha önce de belirtildiği gibi, aşağıdaki üç çözümü kabul edebilir:

  1. Değişmez nesneler kullanın; sınıfınız değişebilir olsa bile, sizin için değişmez kimlikler kullanabilirsiniz. hashcode uygulama ve equals Örneğin, ID benzeri bir değer.
  2. Yukarıdakilere benzer şekilde, uygulayınız add/remove Eklenen nesnenin bir kopyasını almak için, gerçek referans değil. HashSet teklif etmiyor get işlevi (ör. daha sonra nesneyi değiştirmenize izin vermek için); Böylece, güvende olursanız çift kopya olmaz.
  3. Kullanıldıktan sonra onları değiştirmedikleri için egzersiz disiplini,Jon Skeet anlaşılacağı

Ancak, bir nedenden ötürü, bir nesneye eklendikten sonra nesneleri gerçekten değiştirmeniz gerekiyorsa HashSetKoleksiyonunuzu yeni değişikliklerle "bilgilendirmenin" bir yolunu bulmanız gerekiyor. Bu işlevselliği elde etmek için:

  1. Observer tasarım desenini kullanabilir ve genişletebilirsiniz. HashSet uygulamak Observer arayüz. Sizin Member nesneler olmalı Observable ve update  HashSet herhangi bir ayarlayıcıda veya etkileyen başka bir yöntemde hashcode ve / veya equals.

Not 1: 4'ü kullanarak, 4'ü kullanarak: değişiklikleri kabul edebiliriz, ancak zaten var olan bir nesneyi oluşturmayanlar (örneğin, bir kullanıcının kimliğini güncelledim, yeni bir ID atayarak, onu mevcut bir tanesine ayarlamamış olarak). Aksi halde, bir nesnenin, halihazırda var olan başka bir nesneye eşit olacak şekilde dönüştürüldüğü senaryoyu düşünmeniz gerekir. Set. Bu sınırlamayı kabul ederseniz, 4. öneri işe yarayacaktır, aksi takdirde proaktif olmanız ve bu tür durumlar için bir politika tanımlamanız gerekir.

Not 2: Değiştirilen nesnenin hem önceki hem de mevcut durumlarını sağlamanız gerekir. update Uygulama, çünkü eski öğeyi başlangıçta kaldırmak zorundasınız (örn. getClone() yeni değerleri belirlemeden önce), ardından nesneyi yeni duruma ekleyin. Aşağıdaki kod parçacığı yalnızca bir örnek uygulamadır, yinelenen ekleme politikanıza bağlı olarak değişiklik gerektirir.

@Override
public void update(Observable newItem, Object oldItem) {
    remove(oldItem);
    if (add(newItem))
        newItem.addObserver(this);
}

Bir projede birden fazla endeks istediğim projeler üzerinde benzer teknikleri kullandım, böylece ortak bir kimliğe sahip olan Nesnelerin Kümeleri için O (1) ile bakabilirim; HashSets'in bir MultiKeymap'ı olarak hayal edin (bu, daha sonra kesişim / sendika endekslerini kullanabildiğiniz ve SQL benzeri bir aramaya benzer şekilde çalıştığınız için gerçekten yararlıdır). Bu gibi durumlarda, önemli bir değişiklik olduğunda indekslerin her birini FireChange-update gereken yöntemler (genellikle ayarlayıcılar) ekleyerek, endeksler her zaman en son durumlarla güncellenir.


3
2017-12-23 16:53





Teorik olarak (ve neredeyse pratikte değil) daha çok:

  1. kendi alanlarının bir alt kümesinden çıkarılabilecek doğal bir değişmez kimliğe sahiptir, bu durumda bu alanları oluşturmak için bu alanları kullanabilirsiniz. hashCodedan.
  2. Doğal kimlik yoktur, bu durumda Set bunları saklamak gereksizdir, aynı zamanda List.

0
2018-01-17 21:43





Karma temelli bir konteyner yerleştirdikten sonra asla 'yıkanabilir alan' değiştirmeyin.

Sanki (Üye) telefon numaranızı (Member.x) sarı sayfada (karma tabanlı kapsayıcı) kaydetmişsinizdir, ancak numaranızı değiştirmişseniz, o zaman sizi artık sarı sayfada kimse bulamaz.


-1
2018-01-18 01:30