it-swarm.asia

Tam eşleme yapmayan eşittir yönteminde mantığa sahip olmak iyi bir fikir mi?

Bir öğrenciye bir üniversite projesinde yardımcı olurken, aşağıdaki alanlara sahip bir adres için bir sınıf tanımlayan üniversite tarafından sağlanan Java alıştırması) üzerinde çalıştık:

number
street
city
zipcode

Ve sayı ve Posta kodu eşleşirse eşit mantığın true döndürmesi gerektiğini belirtti.

Bir zamanlar eşittir yönteminin sadece nesneler arasında tam bir karşılaştırma yapması gerektiği öğretildi (işaretçiyi kontrol ettikten sonra), bu benim için bir anlam ifade ediyor, ancak verilen görevle çelişiyor.

Kısmi eşleştirmenizle list.contains() gibi şeyleri kullanabilmeniz için neden mantığı geçersiz kılmak istediğinizi anlayabiliyorum, ancak bunun koşer olarak kabul edilip edilmediğini merak ediyorum, neden olmasın?

35
William Dunne

İki Nesne İçin Eşitlik Tanımlama

Eşitlik keyfi olarak iki nesne için tanımlanabilir. Birinin istediği şekilde tanımlamasını yasaklayan katı bir kural yoktur. Bununla birlikte, eşitlik genellikle neyin uygulandığının alan adı kuralları için anlamlı olduğunda tanımlanır.

denklik ilişkisi sözleşmesi :

  • dönüşlü : null olmayan herhangi bir referans değeri için x, x.equals (x) doğru döndürmelidir.
  • simetriktir : x ve y null olmayan referans değerleri için x.equals (y) yalnızca y.equals ( x) true değerini döndürür.
  • geçişlidir : x.equals (y) true ve y.equals ( z) true değerini döndürür, ardından x.equals (z) true değerini döndürmelidir.
  • tutarlıdır : x ve y null olmayan referans değerleri için, x.equals (y) 'nin birden çok çağrılması sürekli olarak true değerini döndürür veya sürekli olarak false değerini döndürür, koşuluyla, nesneler üzerinde eşit karşılaştırmalarda kullanılan hiçbir bilgi değiştirilmez.
  • Null olmayan tüm referans değerleri için x, x.equals (null) false döndürmelidir.

Örneğin, belki aynı posta koduna ve numaraya sahip iki adresi farklı olarak ayırt etmeye gerek yoktur. Aşağıdaki kodun çalışmasını beklemek için tamamen makul olan alanlar vardır:

Address a1 = new Address("123","000000-0","Street Name","City Name");
Address a2 = new Address("123","000000-0","Str33t N4me","C1ty N4me");
assert a1.equals(a2);

Bahsettiğiniz gibi bu, farklı nesneler olmalarını umursamadığınızda yararlı olabilir - yalnızca sahip oldukları değerleri önemsersiniz. Belki posta kodu + sokak numarası doğru adresi tanımlamanız için yeterlidir ve kalan bilgiler "ekstra" dır ve bu ekstra bilgilerin eşitlik mantığınızı etkilemesini istemezsiniz.

Bu bir yazılım için mükemmel bir modelleme olabilir. Bu davranışı sağlamak için bazı belgelerin veya birim testlerinin olduğundan ve genel API'nin bu kullanımı yansıttığından emin olun.


Unutmayın hashCode()

Uygulamaya ilişkin ek bir ayrıntı, birçok dilin karma kodu kavramını yoğun olarak kullanmasıdır. Bu diller, Java dahil, genellikle aşağıdaki öneriyi kabul eder:

X.equals (y) ise, x.hashCode () ve y.hashCode () aynıdır.

Öncekiyle aynı bağlantıdan:

Eşit nesnelerin eşit karma kodlara sahip olması gerektiğini bildiren hashCode yöntemi için genel sözleşmeyi sürdürmek amacıyla, bu yöntem (eşittir) her geçersiz kılındığında hashCode yöntemini geçersiz kılmanın genellikle gerekli olduğunu unutmayın.

Aynı hashCode'a sahip olmanın iki nesnenin eşit olduğu anlamına gelmediğini unutmayın !

Bu anlamda, eşitlik uygulandığında, yukarıda belirtilen özelliği izleyen bir hashCode() de uygulanmalıdır. Bu hashCode(), veri yapıları tarafından verimlilik ve işlemlerinin karmaşıklığı üzerindeki üst sınırları garanti etmek için kullanılır.

İyi bir karma kod fonksiyonu ile gelmek zordur ve kendi başına bir konudur. İdeal olarak iki farklı nesnenin hashCode'u farklı olmalı veya örnek oluşumları arasında eşit bir dağılıma sahip olmalıdır.

Ancak, aşağıdaki basit uygulamanın "iyi" bir karma işlevi olmasa da eşitlik özelliğini hala sağladığını unutmayın:

public int hashCode() {
    return 0;
}

Karma kodunu uygulamanın daha yaygın bir yolu, eşitliğinizi tanımlayan alanların karma kodlarını kullanmak ve bunlarda ikili işlem yapmaktır. Örneğin, posta kodu ve sokak numarası. Genellikle şu şekilde yapılır:

public int hashCode() {
    return this.zipCode.hashCode() ^ this.streetNumber.hashCode();
}

Belirsiz olduğunda, Netliği Seçin

İşte burada eşitlik konusunda ne beklememiz gerektiği konusunda bir ayrım yapıyorum. Farklı insanların eşitlik konusunda farklı beklentileri vardır ve En Az Şaşkınlık Prensibi tasarımınızı daha iyi tanımlamak için diğer seçenekleri düşünebilirsiniz.

Bunlardan hangisi eşit kabul edilmelidir?

Address a1 = new Address("123","000000-0","Street Name","City Name");
Address a2 = new Address("123","000000-0","Str33t N4me","C1ty N4me");
assert a1.equals(a2); // Are typos the same address?
Address a1 = new Address("123","000000-0","John Street","SpringField");
Address a2 = new Address("123","000000-0","John St.","SpringField");
assert a1.equals(a2); // Are abbreviations the same address?
Vector3 v1 = new Vector3(1.0f, 1.0f, 1.0f);
Vector3 v2 = new Vector3(1.0f, 1.0f, 1.0f);
assert v1.equals(v2); // Should two vectors that have the same values be the same?
Vector3 v1 = new Vector3(1.00000001f, 1.0f, 1.0f);
Vector3 v2 = new Vector3(1.0f, 1.0f, 1.0f);
assert v1.equals(v2); // What is the error tolerance?

Doğru ya da yanlış olan her biri için bir dava yapılabilir. Şüphe duyduğunuzda, etki alanı bağlamında daha net olan farklı bir ilişki tanımlanabilir.

Örneğin, isSameLocation(Address a) tanımlayabilirsiniz:

Address a1 = new Address("123","000000-0","John Street","SpringField");
Address a2 = new Address("123","000000-0","John St.","SpringField");

System.out.print(a1.equals(a2)); // false;
System.out.print(a1.isSameLocation(a2)); // true;

Veya Vektörler söz konusu olduğunda, isInRangeOf(Vector v, float range):

Vector3 v1 = new Vector3(1.000001f, 1.0f, 1.0f);
Vector3 v2 = new Vector3(1.0f, 1.0f, 1.0f);

System.out.print(v1.equals(v2)); // false;
System.out.print(v1.isInRangeOf(v2, 0.01f)); // true;

Bu şekilde, eşitlik için tasarım amacınızı daha iyi açıklarsınız ve gelecekteki okuyucuların kodunuzun gerçekte ne yaptığına ilişkin beklentilerini kırmaktan kaçınırsınız. (İnsanların beklentilerinin örneğinizin eşitlik ilişkisine göre nasıl değiştiğini görmek için tüm farklı cevaplara bir göz atabilirsiniz)

89
Albuquerque

Görev amacının operatörün geçersiz kılınmasını araştırmak ve anlamak olduğu üniversite ödevi bağlamındadır. Bu, o zamanın değerli bir egzersiz olarak görünmesini sağlamak için yeterli ima edilen bir örnek ödev gibi görünüyor.

Ancak, bu benim tarafımdan bir kod incelemesi olsaydı, bunu önemli bir tasarım hatası olarak işaretlerdim.

Sorun şu. Açıkça doğru görünen sözdizimsel olarak temiz bir kod sağlar:

if (driverLocation.equals(parcel.deliveryAddress)) { parcel.deliver(); }

Ve diğer kullanıcıların yorumlarına dayanarak, bu kod, posta kodlarının bir sokağa özgü olduğu Brezilya'da doğru sonuçlar üretecektir. Ancak, daha sonra bu varsayımın geçerli olmadığı ABD'de bu yazılımı kullanmayı denediyseniz, bu kod hala doğru görünüyor.

eğer bu şekilde uygulanmışsa:

if (Address.isMatchNumberAndZipcode(driverLocation, parcel.deliveryAddress)) {
  parcel.deliver();
}

birkaç yıl sonra, farklı bir Brezilyalı geliştiriciye kod tabanı verildiğinde ve yazılımın Kaliforniya'daki yeni müşterileri için yanlış adreslere parsel teslim ettiğini söyleyince, şimdi kırık varsayım kodda açıktır ve karar noktasında görülebilir. teslim edilip edilmeyeceği - ki bu bakım programcısının parselin neden yanlış adrese teslim edildiğini görmek için baktığı ilk yer olması muhtemeldir.

Operatör aşırı yüklenmesinde gizli olmayan mantığın gizlenmesi, kod düzeltmesinin daha uzun sürmesini sağlayacaktır. Bu kodda bu sorunu yakalamak için büyük olasılıkla kodda adım adım bir hata ayıklayıcı ile bir oturum alır.

42
Michael Shaw

Eşitlik bir bağlam meselesidir. İki nesnenin eşit kabul edilip edilmeyeceği, ilgili iki nesnede olduğu gibi bir bağlam meselesidir.

Yani, if bağlamınızda şehir ve caddeyi görmezden gelmek mantıklıdır, o zaman eşitliği sadece Posta kodu ve numaraya göre uygulamakta sorun yoktur. (Yorumlardan birinde belirtildiği gibi, Posta kodu ve numarası are Brezilya'daki bir adresi benzersiz olarak tanımlamak için yeterli.)

Elbette, hashCode 'i buna göre aşırı yüklediğinizden emin olmak gibi eşitliği aşırı yüklemek için uygun kuralları izlediğinizden emin olmalısınız.

25
Jörg W Mittag

Bir eşitlik operatörü, yararlı bulduğunuz hususlar nedeniyle iki nesnenin yalnızca ve eşit olarak düşünülmesi gerektiğinde eşit olduğunu iddia edecektir.

Bunu tekrarlayacağım: Yararlı bulduğunuz hususlar nedeniyle.

Yazılım geliştiricisi burada sürücü koltuğunda. Eşitlik operatörü (a = a, a = b, b + a, a = b ve b = c, a = c anlamına gelir) ve karma işleviyle tutarlılık dışında, eşitlik operatörü istediğiniz gibi olabilir.

3
gnasher729

Birçok cevap verilmesine rağmen, benim görüşüm hala mevcut değil.

Bir zamanlar eşittir yönteminin sadece nesneler arasında kesin bir karşılaştırma yapması gerektiği öğretildi

Kuralların söylediklerinin yanı sıra, bu tanım insanların eşitlik hakkında konuştuklarında inisiyasyonlarından aldıkları şeydir. Bazı cevaplar eşitliğin bağlama bağlı olduğunu söylüyor. Alanlarının tümü eşleşmese bile nesnelerin eşit olabileceği bir anlamda haklıdırlar. Ancak "eşittir" in ortak anlayışı çok fazla yeniden tanımlanmamalıdır.

Konuya dönersek, bana aynı konumu gösteriyorsa, başka bir adrese eşit bir adres.

Almanya'da bir şehrin farklı özellikleri olabilir, örneğin bir banliyösü adlandırılmışsa. Daha sonra SUB banliyösünde bir adresin şehri yalnızca "Ana şehir" veya "Ana şehir, SUB" veya sadece "SUB" olarak verilebilir. Ana şehir adını vermek iyi olduğundan, bir şehirdeki tüm sokak adları ve atanan tüm banliyöleri benzersiz olmalıdır.

Burada posta kodu, şehir adı değişse bile şehri anlatmak için yeterlidir.
Ancak posta kodu, genellikle bilmediği bilinen bir sokağa işaret etmedikçe, caddeden ayrılmak benzersiz değildir.
Bu nedenle, farkı göz ardı edilen alanlardan oluşan farklı konumlara işaret edebileceklerse, iki adresi eşit düşünmek sezgisel değildir.

Tüm alanların dışında yalnızca bir kısmı gerektiren bir kullanım durumu varsa, bunu yapan karşılaştırma yöntemi uygun şekilde adlandırılmalıdır. Gizlice dönüştürülmemesi gereken tek bir "eşittir" yöntemi vardır "yalnızca bir özel kullanım durumu için eşittir - ama kimse bunu göremez".

Demek istediğim, açıkladığım nedenlerle ...

ama merak ediyorum bunun kaşer olarak kabul edilip edilmediğini

Yanlışlıkla sokak adlarının önemli olmadığı bir yerde bulunuyorsanız, bilgi olmadan: hayır önemli değil.
Sadece böyle bir yerde kullanılmayan bir şeyi programlamak istiyorsanız: hayır değil.
Öğrencilere bir şeyleri doğru yapma ve kodu anlaşılır ve mantıklı tutma hissi vermek istiyorsanız: hayır değil.

2
puck

Verilen gereksinim insani anlamda ile çelişmekle birlikte, nesnelerin özelliklerinin yalnızca bir alt kümesinin "benzersiz" anlamını tanımlamasına izin vermek TAMAM.

Buradaki sorun, equals() ve hashcode() arasında teknik bir ilişki olması, böylece iki nesne için a ve b bu türün :
if a.equals(b) then a.hashcode()==b.hashcode()
Teklik koşullarınızı tanımlayan özelliklerin bir alt kümesine sahipseniz, hashcode() dönüş değerini hesaplamak için aynı altkümeyi kullanmalısınız.

Sonuçta gereksinim için çok daha uygun bir yaklaşım Comparable hatta özel bir isSame() yöntemi uygulamak olabilir.

1
Timothy Truckle

Değişir.

İyi bir fikir mi ...? Değişir. sadece bir kez, örneğin bir üniversite ödevinde kullanılacak bir uygulama geliştiriyorsanız, (atama incelendikten sonra kodu atacaksanız) iyi bir fikir olabilir. veya bir taşıma yardımcı programı (eski verileri bir kez taşırsınız ve yardımcı programa artık ihtiyacınız yoktur).

Ancak BT endüstrisinde birçok durumda bu kötü bir fikir olacaktır. Neden? @ Jörg W Mittag dedi Eşitlik bir bağlam meselesidir ... eğer bağlamınızda mantıklıysa .... Ancak genellikle aynı nesne birçok farklı bağlamda farklı eşitlik görünümüne sahiptir. Aynı varlığın eşitliğinin ne kadar farklı tanımlanabileceğine dair birkaç örnek:

  • all iki varlığın öznitelikleri olarak
  • İki varlığın birincil anahtarlarının eşitliği olarak
  • Birincil anahtarların eşitliği ve iki varlığın versiyonları
  • Birincil anahtar ve sürüm dışındaki tüm "işletme" özelliklerinin eşitliği olarak

Belirli bir bağlam için eşittir () mantığını uygularsanız, daha sonra bu nesneyi diğer bağlamlarda kullanmak zor olacaktır, çünkü projenizdeki ekiplerdeki birçok geliştirici tam olarak tam olarak hangi bağlamın uygulandığı ve hangi durumlarda ona güvenebilecekleri mantığını bilir. Bazı durumlarda yanlış kullanırlar (@Michael Shaw'un tarif ettiği gibi), diğer durumlarda mantığı görmezden gelir ve aynı amaç için kendi yöntemlerini uygularlar (bu, beklediğinizden farklı olabilir).

Eğer uygulamanız kullanılacaksa daha uzun bir süre için 2-3 yıl gibi, normalde çoklu yeni gereksinimler, çoklu değişiklikler ve çoklu bağlamlar olacaktır. Ve muhtemelen çoklu eşitlik konusunda farklı beklentiler olacaktır. Bu yüzden şunu öneririm:

  • eşittir () biçimsel olarak, iş bağlamıyla bağlantı olmadan, herhangi bir iş mantığı olmadan, tıpkı tüm nesne özniteliklerinin eşitliği anlamına gelir (elbette hashCode/eşittir sözleşmesi izlenmelidir)
  • Her bağlam için isPrimaryKeyAndVersionEqual (), areBusinessAttributesEqual () gibi, bu bağlam anlamında eşitliği uygulayan ayrı bir yöntem sağlayın.

Daha sonra belirli bir bağlamda bir nesneyi bulmak için aşağıdaki gibi karşılık gelen yöntemi kullanırsınız:

if (list.sream.anyMatch(e -> e.isPrimaryKeyAndVersionEqual(myElement))) ...

if (list.sream.anyMatch(e -> e.areBusinessAttributesEqual(myElement))) ...

Böylece kodda daha az hata olacak, kod analizi daha kolay olacak, yeni gereksinimler için uygulamanın değiştirilmesi daha kolay olacaktır.

1
mentallurg

Bahsedilen diğerleri gibi, bir yandan eşitlik sadece bazı özellikleri tatmin eden matematiksel bir kavramdır (bakınız örneğin Albuquerque cevap). Diğer yandan anlambilimi ve uygulaması bağlam tarafından belirlenir.

Uygulama ayrıntılarından bağımsız olarak, örneğin aritmetik ifadeleri temsil eden bir sınıf alın ((1 + 3) * 5 Gibi). Aritmetik ifadeler için standart değerlendirme kurallarını kullanarak bu tür ifadeler için bir tercüman uygularsanız, (1 + 3) * 5 Ve 10 + 10 Örneklerinin equal olması dikkate alınır. Ancak, bu tür ifadeler için güzel bir yazıcı uygularsanız, yukarıdaki örnekler equal olarak kabul edilmezken, (1 + 3) * 5 Ve (1+3)*5 Kabul edilir.

0
michid

Diğerlerinin de belirttiği gibi, nesne eşitliğinin tam anlambilimi, iş alanı tanımının bir parçasıdır. Bu durumda, Address (number, street, city, zipcode) gibi "genel" bir nesneye sahip olmanın makul olduğunu düşünmüyorum (diğerlerinin de belirttiği gibi, Brezilya'da çalışıyor ancak örneğin ABD'de değil).

Bunun yerine, Address eşitlik (tüm üyelerin eşitliği ile tanımlanmış) için değer benzeri semantik var olurdu. Sonra ya:

  1. Yalnızca StreeNumberAndZip ve street içeren ve bunların üzerinde zipCode tanımlayan bir equals sınıfı (_# TODO: bad name_) oluşturun. İki Address nesnesini bu şekilde karşılaştırmak istediğinizde, addressA.streetNumberAndZip().equals(addressB.streetNumberAndZip()) veya ...
  2. Buradaki dar eşitliği tanımlayan bool equalStreeNumberAndZipCode(Address a, Address b) yöntemiyle bir AddressUtils sınıfı oluşturun.

Her iki durumda da, tam eşitlik kontrolü için addressA.equals(addressB) kullanmaya devam edebilirsiniz.

Bir nesnenin n alanları için _2^n_ farklı eşitlik tanımı vardır (her alan kontrole dahil edilebilir veya hariç tutulabilir). Eşitliği birçok farklı şekilde kontrol etmeniz gerektiğini düşünüyorsanız, _enum AddressComponent_ gibi bir şeye sahip olmak da yararlı olabilir. Daha sonra bool addressComponentsAreEqual(EnumSet<AddressComponent> equatedComponents, Address a, Address b) olabilir, böylece şöyle bir şey arayabilirsiniz

_bool addressAreKindOfEqual = AddressUtils.addressComponentsAreEqual(
    new EnumSet.of(
        AddressComponent.streetNumber, 
        AddressComponent.zipCode,
    ),
    addressA, addressB
);
_

Bu çok daha fazla yazımdır, ancak eşitlik kontrol yöntemlerinin üstel bir patlamasından sizi kurtarabilir.

Eşitlik haklı olmak için süptildir ve önemi aldatıcı bir biçimde çok uzundur. Özellikle bir eşitlik operatörünün uygulanmasının aniden nesnenizin setler ve haritalarla Nice oynaması gerektiği anlamına gelir.

Vakaların ezici çoğunluğunda eşitlik kimlik olmalıdır, yani bir nesne ile aynı bellek parçasıysa (- === -) aynı adres. Kimlik ilişkisi her zaman uygun bir eşitlik ilişkisi için tüm koşullara uyar: refleksivite, geçişlilik vb. Kimlik aynı zamanda, sadece iki göstergeyi karşılaştırdığınız için herhangi iki şeyi karşılaştırmanın en hızlı yoludur. Eşitlik ilişkisi sözleşmelerine saygı duyulması, eşitliğin herhangi bir şekilde uygulanamamasıyla ilgili tek ve en önemli şeydir;.

Eşit uygulamanın ikinci yolu, türlerin eşleşip eşleşmediğini karşılaştırmak ve nesnenin her "sahip olunan" alanını karşılaştırmaktır. Bu genellikle her nesnenin ayrıntılarına kadar tekrar eder. Nesneniz eşit olarak adlandırılan veri yapılarına girerse, eşittir muhtemelen bu yaklaşımı kullanırsanız veri yapısının zamanının çoğunu harcar. Başka sorunlar var:

  • nesne değişirse, diğer nesnelerle karşılaştırılmasının sonucu da değişir, bu da standart sınıfların eşitlik hakkında yaptığı her türlü varsayımı kırar;
  • nesneniz bir sınıf/arayüz hiyerarşisindeyse, bu hiyerarşideki iki nesneyi karşılaştırmanın tek aklı yolu, somut türleri tam olarak eşleşiyorsa (bkz. Joshua Bloch'un mükemmel Etkili Java bununla ilgili daha fazla bilgi için kitap);
  • eşitlik ilişkisini olabildiğince çok alan ekleyerek çok katı hale getirmeye çalışırsanız, sonunda eşitliğinizin "eşlik" iş mantığına karşılık gelmediği bir duruma düşersiniz.

Üçüncü yol, sadece iş mantığıyla ilgili alanları seçmek ve geri kalanını görmezden gelmek olacaktır. Bu yaklaşımın kırılma olasılığı keyfi olarak 1'e yakındır. Başkaları tarafından belirtildiği gibi ilk neden, bir bağlamında anlamlı olan bir karşılaştırmanın t tüm bağlamlarda mantıklı gelmelidir. Dil, bir eşitlik oluşturmanızı ister, bu nedenle tüm bağlamlarda daha iyi çalışır. Adresler için böyle bir karşılaştırma mantığı yoktur. "Bu iki adres görünüm özdeş" yöntemlerini özelleştirmiş olabilirsiniz, ancak bu tür bir yöntemin dil destekli olduğu gibi karşılaştırmamalısınız. kaçınılmaz olarak okuyucuları şaşırtacak.

Ayrıca Falsehoods programcılarının adresler hakkında inandıklarına bir göz atmanızı da tavsiye ederim: https://www.mjt.me.uk/posts/falsehoods-programmers-believe-about-addresses/ Eğlenceli bir okuma ve bazı tuzaklardan kaçınmanıza yardımcı olabilir.

0
Kafein