Yöntem, türdeki başka bir yöntemle aynı silme özelliğine sahiptir


381

Aynı sınıfta aşağıdaki iki yönteme sahip olmak neden yasal değil?

class Test{
   void add(Set<Integer> ii){}
   void add(Set<String> ss){}
}

Ben alıyorum compilation error

Yöntem add (Set), Test türündeki başka bir yöntemle aynı silme add (Set) değerine sahiptir.

etrafında çalışabilirken, javac'ın neden bundan hoşlanmadığını merak ediyordum.

Birçok durumda, bu iki yöntemin mantığının çok benzer olacağını ve yerine tek bir

public void add(Set<?> set){}

ancak bu her zaman böyle değildir.

Bu constructorsargümanları alan iki tane almak istiyorsanız bu çok can sıkıcı bir durumdur, çünkü o zaman sadece bir tanesinin adını değiştiremezsiniz constructors.


1
veri yapılarınız biterse ve daha fazla sürüme ihtiyacınız varsa ne olacak?
Omry Yadan

1
Temel sürümlerden devralınan özel sınıflar yapabilirsiniz.
willlma

1
OP, yapıcı problemine bir çözüm buldun mu? İki çeşit kabul etmem gerekiyor Listve bununla nasıl başa çıkacağımı bilmiyorum.
Tomáš Zato - Monica'yı

9
Java ile çalışırken, gerçekten C # ...
Svish

1
@ TomášZato, yapıcıya kukla params ekleyerek çözdüm: Boolean noopSignatureOverload.
Nthalk

Yanıtlar:


357

Bu kural, hala ham türleri kullanan eski koddaki çakışmalardan kaçınmayı amaçlamaktadır.

JLS'den çekilmesine neden izin verilmediğini gösteren bir örnek . Diyelim ki, jenerikler Java ile tanıştırılmadan önce, böyle bir kod yazdım:

class CollectionConverter {
  List toList(Collection c) {...}
}

Sınıfımı genişletiyorsun, şöyle:

class Overrider extends CollectionConverter{
  List toList(Collection c) {...}
}

Jeneriklerin tanıtılmasından sonra kütüphanemi güncellemeye karar verdim.

class CollectionConverter {
  <T> List<T> toList(Collection<T> c) {...}
}

Herhangi bir güncelleme yapmaya hazır değilsiniz, bu yüzden Overridersınıfınızı yalnız bırakırsınız . toList()Yöntemi doğru bir şekilde geçersiz kılmak için , dil tasarımcıları ham türün herhangi bir türetilen türe "geçersiz kılmaya eşdeğer" olduğuna karar verdiler. Bu, yöntem imzanız artık resmi olarak benim sınıfımın imzasına eşit olmasa da, yönteminizin hala geçersiz kıldığı anlamına gelir.

Şimdi zaman geçiyor ve sınıfınızı güncellemeye hazır olduğunuza karar veriyorsunuz. Ama biraz sıkıyorsunuz ve mevcut ham toList()yöntemi düzenlemek yerine , böyle yeni bir yöntem ekliyorsunuz :

class Overrider extends CollectionConverter {
  @Override
  List toList(Collection c) {...}
  @Override
  <T> List<T> toList(Collection<T> c) {...}
}

Ham türlerin geçersiz kılma denkliği nedeniyle, her iki yöntem de toList(Collection<T>)yöntemi geçersiz kılmak için geçerli bir formdadır . Ancak elbette, derleyicinin tek bir yöntemi çözmesi gerekiyor. Bu belirsizliği ortadan kaldırmak için, sınıfların geçersiz kılmaya eşdeğer olan birden fazla yöntemi olmasına izin verilmez - yani, silindikten sonra aynı parametre türlerine sahip birden çok yöntem bulunur.

Anahtar, bu, ham türleri kullanarak eski kodla uyumluluğu korumak için tasarlanmış bir dil kuralıdır. Tür parametrelerinin silinmesinin gerektirdiği bir sınırlama değildir; yöntem çözünürlüğü derleme zamanında gerçekleştiğinden, yöntem tanımlayıcısına genel türler eklemek yeterli olacaktır.


3
Harika cevap ve örnek! Ancak, son cümlenizi tam olarak anlarsam emin değilim ("Yöntem çözünürlüğü derleme zamanında gerçekleştiğinden, silinmeden önce, bu işi yapmak için tür yeniden tanımlaması gerekmez."). Biraz ayrıntı verebilir misiniz?
Jonas Eicher

2
Mantıklı. Sadece şablon yöntemlerinde tür yeniden yapılandırması hakkında düşünmek için biraz zaman geçirdim, ancak evet: derleyici, tür silinmeden önce doğru yöntemin seçilmesini sağlar. Güzel. Eski kod uyumluluğu sorunları tarafından lekelenmemişse.
Jonas Eicher

1
@daveloyall Hayır, böyle bir seçeneğin farkında değilim javac.
erickson

13
Java hatasıyla ilk karşılaştığımda hiç hata yok ve sadece Java yazarları herkesin yaptığı gibi uyarıları kullanırsa derlenebilir. Sadece her şeyi daha iyi bildiklerini düşünüyorlar.
Tomáš Zato - Monica'yı eski durumuna döndür

1
@ TomášZato Hayır, bunun için temiz bir çözüm bilmiyorum. Kirli bir şey istiyorsanız , türünü belirtmek için tanımladığınız List<?>ve bazılarını geçebilirsiniz enum. Türe özgü mantık aslında numaralandırmada bir yöntemde olabilir. Alternatif olarak, iki farklı kurucu içeren bir sınıf yerine iki farklı sınıf oluşturmak isteyebilirsiniz. Ortak mantık, her iki türün temsil ettiği bir üst sınıfta veya yardımcı bir nesnede olacaktır.
erickson

118

Java jenerikleri tip silme kullanır. Köşeli parantez ( <Integer>ve <String>) içindeki bit kaldırılır, böylece aynı imzası olan iki yöntemle karşılaşırsınız ( add(Set)hatada görürsünüz). Buna izin verilmez, çünkü çalışma zamanı her vaka için hangisini kullanacağını bilemez.

Java birleşik jenerikler alırsa, bunu yapabilirdiniz, ancak muhtemelen bu muhtemelen mümkün değildir.


24
Üzgünüm ama cevabınız (ve diğer cevaplarınız) burada neden bir hata olduğunu açıklamıyor. Aşırı yük çözünürlüğü derleme zamanında yapılır ve derleyici mutlaka hangi yöntemin adrese veya hangi yönteme atıfta bulunmadığına inandığım tarafından atıfta bulunulacağına karar vermek için gereken tür bilgisine sahiptir. Hatta bazı derleyiciler bunun derlenmesine izin vereceğini düşünüyorum.
Stilgar

5
@Stilgar yansıma yoluyla çağrılan veya denetlenen yöntemin durdurulması gereken nedir? Class.getMethods () tarafından döndürülen yöntemlerin listesinde iki özdeş yöntem bulunur ve bu anlamlı olmaz.
Adrian Mouat

5
Yansıtma bilgileri, jeneriklerle çalışmak için gereken meta verileri içerebilir / içermelidir. Değilse, Java derleyicisi zaten derlenmiş kitaplığı içe aktardığınızda genel yöntemler hakkında ne biliyor?
Stilgar

4
Ardından getMethod yönteminin düzeltilmesi gerekir. Örneğin, genel aşırı yüklemeyi belirten bir aşırı yük tanıtın ve orijinal yöntemin yalnızca jenerik olmayan sürümü döndürmesini sağlayın, genel olarak eklenmiş herhangi bir yöntemi döndürmeyin. Tabii ki bu 1.5 sürümünde yapılmış olmalıdır. Şimdi yaparlarsa yöntemin geriye dönük uyumluluğunu kırarlar. Benim deyimin yanında tip silme bu davranışı dikte etmez. Muhtemelen sınırlı kaynaklardan dolayı yeterli iş bulamayan uygulama.
Stilgar

1
Bu kesin bir cevap değildir, ancak sorunu hızlı bir şekilde yararlı bir kurguda özetlemektedir: yöntem imzaları çok benzer, derleyici farkı söyleyemeyebilir ve sonra "çözülmemiş derleme sorunları" elde edersiniz.
worc

46

Bunun nedeni Java Generics'in Type Erasure ile uygulanmasıdır .

Yöntemleriniz derleme zamanında aşağıdaki gibi bir dile çevrilir:

Yöntem çözünürlüğü derleme zamanında gerçekleşir ve tür parametrelerini dikkate almaz. ( erickson'ın cevabına bakınız )

void add(Set ii);
void add(Set ss);

Her iki yöntem de tip parametreleri olmadan aynı imzayı taşır, dolayısıyla hata.


21

Sorun şu ki Set<Integer>ve Set<String>aslında SetJVM bir olarak muamele . Küme için bir tür seçmek (sizin durumunuzda Dize veya Tamsayı) yalnızca derleyici tarafından kullanılan sözdizimsel şekerdir. JVM Set<String>ve arasında ayrım yapamaz Set<Integer>.


7
JVM çalışma zamanının her birini ayıracak hiçbir bilgiye sahip olmadığı doğrudur Set, ancak yöntem çözünürlüğü derleme zamanında gerçekleştiğinden, gerekli bilgiler mevcut olduğunda, bu önemli değildir. Sorun, bu aşırı yüklemelere izin vermek, ham türler için ödenekle çakışacaktı, bu yüzden Java sözdiziminde yasadışı hale getirildiler.
erickson

@erickson Derleyici hangi yöntemi arayacağını bilse bile, bayt kodunda olduğu gibi olamaz, ikisi de tamamen aynı görünür. (Ljava/util/Collection;)Ljava/util/List;Çalışmadığı için bir yöntem çağrısının belirtilme şeklini değiştirmeniz gerekir . Kullanabilirsiniz (Ljava/util/Collection<String>;)Ljava/util/List<String>;, ancak bu uyumsuz bir değişikliktir ve sahip olduğunuz her şeyin silinmiş bir tür olduğu yerlerde çözülemez sorunlarla karşılaşırsınız. Muhtemelen silme işlemini tamamen bırakmak zorunda kalacaksınız, ancak bu oldukça karmaşık.
maaartinus

@maaartinus Evet, yöntem belirleyicisini değiştirmeniz gerektiğini kabul ediyorum. Bu girişimden vazgeçmelerine neden olan çözülemeyen sorunlardan bazılarına ulaşmaya çalışıyorum.
erickson

7

Gibi bir tür olmadan tek bir Yöntem tanımlayın void add(Set ii){}

Seçiminize göre yöntemi çağırırken bu türden bahsedebilirsiniz. Her türlü set için çalışacaktır.


3

Derleyicinin java bayt kodunda Set (Integer) 'ı Set (Object)' e çevirmesi mümkün olabilir. Bu durumda, Set (Integer) yalnızca sözdizimi denetimi için derleme aşamasında kullanılır.


5
Teknik olarak sadece ham tip Set. Jenerikler bayt kodunda mevcut değildir, döküm için sözdizimsel şekerdir ve derleme zamanı türü güvenliği sağlar.
Andrzej Doyle

1

Böyle bir şey yazmaya çalıştığımda buna çarptım: Continuable<T> callAsync(Callable<T> code) {....} Ve Continuable<Continuable<T>> callAsync(Callable<Continuable<T>> veryAsyncCode) {...} derleyici için 2 tanımı Continuable<> callAsync(Callable<> veryAsyncCode) {...}

Tür silme, kelimenin tam anlamıyla tür argümanlarının jeneriklerden silinmesi anlamına gelir. Bu ÇOK sinir bozucu, ama bu süre Java ile olacak bir sınırlamadır. Yapıcılar için çok fazla şey yapılamaz, örneğin yapıcıda farklı parametrelerle uzmanlaşmış 2 yeni alt sınıf. Veya bunun yerine başlatma yöntemlerini kullanın ... (sanal kurucular?) Farklı adlarla ...

benzer işlem yöntemleri için yeniden adlandırma,

class Test{
   void addIntegers(Set<Integer> ii){}
   void addStrings(Set<String> ss){}
}

Veya biraz daha açıklayıcı isimlerle, oyu durumlar için gibi kendinden belgeleyen addNamesve addIndexesya böyle.

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.