Java Koleksiyonları neden genel yöntemleri kaldırmıyor?


144

Neden değil Collection.remove (Object o) jenerik?

Sahip Collection<E>olabilir gibi görünüyorboolean remove(E o);

Sonra, yanlışlıkla Set<String>her bir String yerine bir ( örneğin) kaldırmaya çalıştığınızda, Collection<String>daha sonra hata ayıklama sorunu yerine derleme zamanı hatası olur.


13
Bu, otomatik boksla birleştirdiğinizde sizi gerçekten ısırabilir. Listeden bir şey kaldırmaya çalışırsanız ve bunu int yerine bir Tamsayı iletirseniz, remove (Object) yöntemini çağırır.
ScArcher2

Yanıtlar:


73

Josh Bloch ve Bill Pugh bu konuya Java Puzzlers IV: Fantom Referans Tehdit, Klonun Saldırısı ve Vardiya İntikamı'nda değiniyor .

Josh Bloch (6:41) Harita'nın get yöntemini, yöntemi kaldırmayı ve diğer bazı yöntemleri doğrulamaya çalıştıklarını söylüyor, ancak "basitçe işe yaramadı".

Yalnızca koleksiyonun genel türüne parametre türü olarak izin verirseniz, üretilemeyen çok sayıda makul program vardır. Onun tarafından verilen örnek bir kesişimidir Listait Numbers ve Listbir Longs.


6
Add () neden yazılan bir parametreyi alabilir ancak remove () yapamayacağım hala kavrayışımın biraz ötesinde. Josh Bloch Koleksiyonlar soruları için kesin referans olacaktır. Benzer bir koleksiyon uygulaması yapmaya ve kendim görmeye çalışmadan elde edebileceğim her şey olabilir. :( Teşekkürler.
Chris Mazzola

2
Chris - Java Generics Eğitim PDF dosyasını okuyun, nedenini açıklayacaktır.
JeeBee

42
Aslında, çok basit! Add () yanlış bir nesne aldıysa, koleksiyon kırılır. İçinde olması gerekmeyen şeyler içeriyor! Bu remove () için geçerli değildir veya () içerir.
Kevin Bourrillion

12
Bu arada, bu temel kural - yalnızca koleksiyonda gerçek hasarı önlemek için tip parametrelerini kullanarak - tüm kütüphanede kesinlikle tutarlı bir şekilde takip edilir.
Kevin Bourrillion

3
@KevinBourrillion: "Gerçek hasar" kuralının bile var olduğunu fark etmeden yıllardır (hem Java hem de C #) jeneriklerle çalışıyorum ... ama şimdi gördüğüm kadarıyla doğrudan% 100 mükemmel mantıklı olduğunu belirtti. Balık için teşekkürler !!! Şimdilik bazı yöntemlerin dejenere edilip edilemeyeceğini ve bu nedenle değiĢtirilmesinin gerekip gerekmediğini görmek için geri dönüp uygulamalarıma bakmaya mecbur hissediyorum. İç çekmek.
corlettk

74

remove()(içinde Mapolduğu gibi içinde Collection) genel değildir, çünkü herhangi bir nesne türüne geçebilmeniz gerekir remove(). Kaldırılan nesnenin, ilettiğiniz nesne ile aynı tür olması gerekmez remove(); sadece eşit olmalarını gerektirir. Spesifikasyonuna kaynaktan remove(), remove(o)nesne kaldırır egibi (o==null ? e==null : o.equals(e))olduğu true. Aynı türden gerektiren ove eolmayacak bir şey olmadığını unutmayın . Bu, equals()yöntemin Objectyalnızca nesne ile aynı türde değil, bir as parametresini alması gerçeğinden kaynaklanır .

Bununla birlikte, birçok sınıfın equals(), nesnelerinin yalnızca kendi sınıfındaki nesnelere eşit olabilmesi için tanımladığı genel olarak doğru olabilir, ancak bu her zaman böyle değildir. Örneğin, için belirtim List.equals(), iki List nesnesinin hem Listeler hem de farklı uygulamaları olsa bile aynı içeriğe sahip olması durumunda eşit olduğunu belirtir List. Bu sorudaki örneğe dönersek, bir Map<ArrayList, Something>ve benim için remove()bir LinkedListargüman olarak çağırmak mümkündür ve aynı içeriğe sahip bir liste olan anahtarı kaldırmalıdır. Bu remove(), genel ve argüman türünü kısıtlamış olsaydı mümkün olmazdı .


1
Ancak Haritayı Harita <Liste, Bir Şey> (ArrayList yerine) olarak tanımlayacak olsaydınız, LinkedList kullanarak kaldırmak mümkün olurdu. Bence bu cevap yanlış.
AlikElzin-kilaka

3
Cevap doğru, fakat eksik görünüyor. Sadece equals()yöntemi neden jenerikleştirmediklerini sorar. Bu "liberter" yaklaşım yerine güvenlik türüne daha fazla fayda görebiliyordum. Mevcut uygulama ile çoğu durumda, remove()yöntemin getirdiği bu esneklik neşeli olmaktan ziyade kodumuza alma hataları içindir .
kellogs

2
@kellogs: " equals()Yöntemi genelleştirmek" ile ne demek istersiniz ?
newacct

5
@MattBall: "Burada T bildiren sınıftır" Ancak Java'da böyle bir sözdizimi yoktur. Tsınıfta bir tür parametresi olarak bildirilmelidir ve Objecttür parametresi yoktur. "Bildiren sınıfa" atıfta bulunan bir türe sahip olmanın bir yolu yoktur.
15:15 tarihinde newacct

3
Ben Kellogs eşitlik halinde genel arabirimi nelerdi söylediğini düşünüyorum Equality<T>ile equals(T other). O zaman sahip olabilirsiniz remove(Equality<T> o)ve osadece diğeriyle karşılaştırılabilecek bir nesnedir T.
weston

11

Çünkü type parametreniz bir joker karakterse, genel bir kaldırma yöntemi kullanamazsınız.

Map 'get (Object) yöntemi ile bu soruya çalışan hatırlamak gibi görünüyor. Bu durumda get yöntemi genel değildir, ancak ilk tür parametresiyle aynı türden bir nesnenin geçirilmesini makul olarak beklemelidir. Haritalar'ı ilk tür parametresi olarak joker karakterle geçiriyorsanız, bu argümanın genel olması durumunda bu yöntemle Haritadan bir öğe almanın bir yolu olmadığını fark ettim. Joker karakter bağımsız değişkenleri gerçekten tatmin edilemez, çünkü derleyici türün doğru olduğunu garanti edemez. Eklemenin genel nedeninin, koleksiyona eklemeden önce türün doğru olduğunu garanti etmenizi beklediğiniz olduğunu tahmin ediyorum. Ancak, bir nesneyi kaldırırken, tür yanlışsa, hiçbir şeyle eşleşmez.

Muhtemelen çok iyi açıklamamıştım, ama bana yeterince mantıklı geliyor.


1
Bu konuyu biraz açıklayabilir misiniz?
Thomas Owens

6

Diğer cevaplara ek olarak, yöntemin bir kabul etmesinin başka bir nedeni vardır Object, bu da öngörülmektedir. Aşağıdaki örneği düşünün:

class Person {
    public String name;
    // override equals()
}
class Employee extends Person {
    public String company;
    // override equals()
}
class Developer extends Employee {
    public int yearsOfExperience;
    // override equals()
}

class Test {
    public static void main(String[] args) {
        Collection<? extends Person> people = new ArrayList<Employee>();
        // ...

        // to remove the first employee with a specific name:
        people.remove(new Person(someName1));

        // to remove the first developer that matches some criteria:
        people.remove(new Developer(someName2, someCompany, 10));

        // to remove the first employee who is either
        // a developer or an employee of someCompany:
        people.remove(new Object() {
            public boolean equals(Object employee) {
                return employee instanceof Developer
                    || ((Employee) employee).company.equals(someCompany);
        }});
    }
}

Mesele şu ki, removeyönteme iletilen nesne yöntemin tanımlanmasından sorumludur equals. Tahminler oluşturmak bu şekilde çok basitleşir.


Yatırımcı? (Filler filler filler)
Matt R

3
Liste, yourObject.equals(developer)Koleksiyonlar API'sında belgelendiği gibi uygulanır : java.sun.com/javase/6/docs/api/java/util/…
Hosam Aly

13
Bu benim için bir taciz gibi görünüyor
RAY

7
Yüklem nesneniz equalsyöntemin sözleşmesini , yani simetriyi bozduğundan kötüye kullanımdır . Kaldırma yöntemi yalnızca nesneleriniz equals / hashCode spesifikasyonunu yerine getirdiği sürece spesifikasyonuna bağlıdır, bu nedenle herhangi bir uygulama karşılaştırmayı başka bir şekilde yapmakta serbesttir. Ayrıca, yüklem nesneniz .hashCode()yöntemi uygulamaz (eşit olarak tutarlı bir şekilde uygulanamaz), bu nedenle kaldırma çağrısı hiçbir zaman Hash tabanlı bir koleksiyonda (HashSet veya HashMap.keys () gibi) çalışmayacaktır. ArrayList ile çalışması saf şanstır.
Paŭlo Ebermann

3
(Genel tip soruyu tartışmıyorum - bu daha önce şaşırmıştı - sadece burada tahminler için eşit kullanımınız.) Tabii ki HashMap ve HashSet karma kodunu kontrol ediyor ve TreeSet / Map öğelerin sırasını kullanıyor . Yine de, Collection.removesözleşmesini bozmadan tam olarak uygularlar (sipariş eşitse tutarlıysa). Ve döndürülen eşittir çağrısı ile çeşitli bir ArrayList (veya AbstractCollection, sanırım) yine de sözleşmeyi doğru bir şekilde uygulayacaktır - equalssözleşmeyi ihlal ettiğiniz için, amaçlandığı gibi çalışmazsa sizin hatanızdır .
Paŭlo Ebermann

5

Biri bir koleksiyona sahiptir varsayalım Catve türlerinden bazıları nesne referanslarını Animal, Cat, SiameseCat, ve Dog. Koleksiyona, Catveya SiameseCatbaşvuru tarafından belirtilen nesneyi içerip içermediğini sormak makul görünmektedir. AnimalReferans tarafından atıfta bulunulan nesneyi içerip içermediğini sormak tehlikeli görünebilir, ancak yine de tamamen makul. Sonuçta, söz konusu nesne bir a Catolabilir ve koleksiyonda görünebilir.

Ayrıca, nesne a dışında bir şey olsa bile Cat, koleksiyonda görünüp görünmediğini söylemekte bir sorun yoktur - sadece "hayır, değil" şeklinde yanıtlayın. Bir türün "arama stili" koleksiyonu, herhangi bir üst tipin referansını anlamlı bir şekilde kabul edebilmeli ve nesnenin koleksiyon içinde var olup olmadığını belirleyebilmelidir. Aktarılan nesne başvurusu ilgisiz bir türdeyse, koleksiyonun onu içerme olasılığı yoktur, bu nedenle sorgu bir anlamda anlamlı değildir (her zaman "hayır" yanıtını verecektir). Bununla birlikte, parametreleri alt türler veya üst türler ile sınırlamanın bir yolu olmadığından, herhangi bir türü kabul etmek ve türü koleksiyonun alakası olmayan nesneler için "hayır" yanıtını vermek en pratik yöntemdir.


1
"Aktarılan nesne başvurusu alakasız bir türdeyse, koleksiyonun onu içerme olasılığı yoktur" Yanlış. Sadece ona eşit bir şey içermelidir; ve farklı sınıflardaki nesneler eşit olabilir.
newacct

"çok tehlikeli görünebilir, ama yine de mükemmel bir şekilde makul" Öyle mi? Bir türden bir nesnenin her zaman başka türden bir nesneyle eşitlik açısından kontrol edilemediği bir dünya düşünün, çünkü hangi türe eşit olabileceği parametrelendirilir ( Comparablekarşılaştırılabilecek türler için nasıl parametrelendirilir). O zaman insanların alakasız bir şeyden geçmelerine izin vermek makul olmazdı.
newacct

@newacct: Büyüklük karşılaştırması ve eşitlik karşılaştırması arasında temel bir fark vardır: verilen nesneler Ave Bbir tür Xve Ydiğeri, A> Bve X> Y. Ya A> Yve Y< Aveya X> Bve B< X. Bu ilişkiler ancak büyüklük karşılaştırmaları her iki türü de biliyorsa var olabilir. Buna karşılık, bir nesnenin eşitlik karşılaştırma yöntemi, söz konusu diğer tür hakkında hiçbir şey bilmek zorunda kalmadan kendisini herhangi bir türden herhangi birine eşitsiz olarak ilan edebilir. Cat
Türdeki

... bir tür nesneden "daha büyük" veya "daha küçük" FordMustang, ancak bunun böyle bir nesneye eşit olup olmadığını söylemekte zorlanmamalıdır (cevap, açıkçası, "hayır" olmaktır).
supercat

4

Her zaman bunun, remove () öğesinin ne tür bir nesne verdiğinize bakmak için bir nedeni olmadığı için olduğunu düşündüm. Her ne olursa olsun, o nesnenin Koleksiyon'un içerdiklerinden biri olup olmadığını kontrol etmek yeterince kolaydır, çünkü herhangi bir şeye eşittir () diyebilir. Yalnızca bu türden nesneler içerdiğinden emin olmak için add () üzerindeki türü kontrol etmek gerekir.


0

Bir uzlaşma oldu. Her iki yaklaşımın da avantajları vardır:

  • remove(Object o)
    • daha esnektir. Örneğin, bir sayı listesi üzerinden yineleme yapmayı ve bunları uzun listeden çıkarmayı sağlar.
    • Bu esnekliği kullanan kod daha kolay oluşturulabilir
  • remove(E e) çoğu programın derleme zamanında küçük hataları algılayarak ne yapmak istediğine daha fazla tür güvenliği getirir, örneğin yanlışlıkla bir tamsayıyı şort listesinden çıkarmaya çalışmak gibi.

Java API'sini geliştirirken geriye dönük uyumluluk her zaman önemli bir hedefti, bu nedenle mevcut kodun üretilmesini kolaylaştırdığı için remove (Object o) seçildi. Geriye dönük uyumluluk bir sorun OLMADIysa, tasarımcıların kaldırmayı (E e) seçeceğini tahmin ediyorum.


-1

Remove genel bir yöntem değildir, bu nedenle genel olmayan bir koleksiyon kullanan mevcut kod hala derlenir ve aynı davranışa sahiptir.

Ayrıntılar için http://www.ibm.com/developerworks/java/library/j-jtp01255.html adresine bakın.

Düzenleme: Bir yorumcu, ekleme yönteminin neden genel olduğunu sorar. [... açıklamam kaldırıldı ...] İkinci yorumcu firebird84'ten benden çok daha iyi bir soru yanıtladı.


2
Öyleyse neden add yöntemi geneldir?
Bob Gettys

@ firebird84 remove (Object) yanlış türde nesneleri yok sayar, ancak remove (E) derleme hatasına neden olur. Bu davranışı değiştirir.
Nuh

: omuz silkme: - Çalışma zamanı davranış olduğu değil değişti; derleme hatası çalışma zamanı davranışı değildir . Add yönteminin "davranışı" bu şekilde değişir.
Jason S

-2

Başka bir neden arabirimlerdir. İşte bunu gösteren bir örnek:

public interface A {}

public interface B {}

public class MyClass implements A, B {}

public static void main(String[] args) {
   Collection<A> collection = new ArrayList<>();
   MyClass item = new MyClass();
   collection.add(item);  // works fine
   B b = item; // valid
   collection.remove(b); /* It works because the remove method accepts an Object. If it was generic, this would not work */
}

Kaçabileceğiniz bir şey gösteriyorsunuz çünkü remove()kovaryant değil. Soru olsa da, bu olup olmadığıdır gerektiğini izin verilebilir. ArrayList#remove()referans eşitliğine değil, değer eşitliğine göre çalışır. Neden a'nın bir Bdeğerine eşit olmasını beklersiniz A? Örneğinizde olabilir , ancak bu garip bir beklenti. MyClassBurada bir argüman sunduğunu görmeyi tercih ederim .
seh

-3

Çünkü mevcut (Java5 öncesi) kodu kıracaktır. Örneğin,

Set stringSet = new HashSet();
// do some stuff...
Object o = "foobar";
stringSet.remove(o);

Şimdi yukarıdaki kodun yanlış olduğunu söyleyebilirsiniz, ancak o'nun heterojen bir nesne kümesinden geldiğini varsayalım (yani, dizeler, sayı, nesneler vb. İçeriyordu). Tüm eşleşmeleri kaldırmak istiyorsunuz, çünkü yasal değil, çünkü silme eşit olmayan dizeleri görmezden geliyordu. Ancak (String o) öğesini kaldırırsanız, bu artık çalışmaz.


4
Bir Liste başlatırsam <String> Ben sadece List.remove (someString); Geriye dönük uyumluluğu desteklemem gerekiyorsa, ham bir List - List <?> Kullanırdım sonra list.remove (someObject) 'i çağırabilir miyim?
Chris Mazzola

5
"Remove"
ifadesini
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.