Dlaczego posiadanie następujących dwóch metod w tej samej klasie jest niezgodne z prawem?
class Test{
void add(Set<Integer> ii){}
void add(Set<String> ss){}
}
Rozumiem compilation error
Metoda add (Set) ma to samo dodanie kasowania (Set), co inna metoda w teście typu.
chociaż mogę sobie z tym poradzić, zastanawiałem się, dlaczego javac tego nie lubi.
Widzę, że w wielu przypadkach logika tych dwóch metod byłaby bardzo podobna i mogłaby zostać zastąpiona jedną
public void add(Set<?> set){}
metoda, ale nie zawsze tak jest.
Jest to szczególnie denerwujące, jeśli chcesz mieć dwa, constructors
które biorą te argumenty, ponieważ wtedy nie możesz po prostu zmienić nazwy jednego z nich constructors
.
List
i nie wiem, jak sobie z tym poradzić.Odpowiedzi:
Ta reguła ma na celu uniknięcie konfliktów w starszym kodzie, który nadal używa typów raw.
Oto przykład, dlaczego nie było to dozwolone, zaczerpnięty z JLS. Załóżmy, że przed wprowadzeniem generics do Javy napisałem taki kod:
Rozszerzasz moją klasę w ten sposób:
Po wprowadzeniu ogólnych, postanowiłem zaktualizować swoją bibliotekę.
Nie jesteś gotowy na żadne aktualizacje, więc zostaw
Overrider
klasę w spokoju. Aby poprawnie przesłonićtoList()
metodę, projektanci języków zdecydowali, że typ surowy jest „zastępujący odpowiednik” dla dowolnego generowanego typu. Oznacza to, że chociaż podpis twojej metody nie jest już formalnie równy podpisowi mojej nadklasy, twoja metoda wciąż zastępuje.Teraz czas mija i decydujesz, że jesteś gotowy, aby zaktualizować swoją klasę. Ale trochę spieprzysz i zamiast edytować istniejącą, surową
toList()
metodę, dodajesz nową metodę taką:Z powodu zastąpienia równoważności typów surowych obie metody są w prawidłowej formie, aby zastąpić
toList(Collection<T>)
metodę. Ale oczywiście kompilator musi rozwiązać jedną metodę. Aby wyeliminować tę dwuznaczność, klasy nie mogą mieć wielu metod, które są równoważne z nadpisaniem - to znaczy wielu metod z tymi samymi typami parametrów po skasowaniu.Kluczem jest to, że jest to reguła językowa zaprojektowana w celu zachowania zgodności ze starym kodem używającym typów raw. Nie jest to ograniczenie wymagane przez usunięcie parametrów typu; ponieważ rozpoznawanie metod zachodzi w czasie kompilacji, dodanie typów ogólnych do identyfikatora metody byłoby wystarczające.
źródło
javac
.List<?>
i niektóreenum
, które zdefiniujesz, aby wskazać typ. Logika specyficzna dla typu mogłaby faktycznie być metodą w wyliczeniu. Alternatywnie, możesz chcieć utworzyć dwie różne klasy, zamiast jednej klasy z dwoma różnymi konstruktorami. Wspólna logika byłaby w nadklasie lub obiekcie pomocniczym, któremu delegują oba typy.Generics Java używa usuwania typu. Bit w nawiasach kątowych (
<Integer>
i<String>
) zostaje usunięty, więc otrzymujesz dwie metody o identycznej sygnaturze (add(Set)
widoczne w błędzie). Jest to niedozwolone, ponieważ środowisko wykonawcze nie wiedziałoby, którego użyć dla każdej sprawy.Jeśli Java kiedykolwiek zostanie zweryfikowana, możesz to zrobić, ale prawdopodobnie teraz jest to mało prawdopodobne.
źródło
Wynika to z faktu, że Java Generics są implementowane za pomocą Type Erasure .
Twoje metody zostaną przetłumaczone w czasie kompilacji na coś takiego:Rozdzielczość metody występuje w czasie kompilacji i nie uwzględnia parametrów typu. ( patrz odpowiedź Ericksona )
Obie metody mają tę samą sygnaturę bez parametrów typu, stąd błąd.
źródło
Problemem jest to, że
Set<Integer>
iSet<String>
są właściwie traktowane jakoSet
z JVM. Wybranie typu dla zestawu (w twoim przypadku łańcuch lub liczba całkowita) to tylko cukier syntaktyczny używany przez kompilator. JVM nie może rozróżnić międzySet<String>
iSet<Integer>
.źródło
Set
, ale ponieważ rozpoznawanie metod odbywa się w czasie kompilacji, gdy dostępne są niezbędne informacje, nie ma to znaczenia. Problem polega na tym, że zezwolenie na te przeciążenia powodowałoby konflikt z tolerancją dla typów surowych, dlatego zostały one uznane za nielegalne w składni Java.(Ljava/util/Collection;)Ljava/util/List;
nie działa. Możesz użyć(Ljava/util/Collection<String>;)Ljava/util/List<String>;
, ale jest to niezgodna zmiana i napotkasz nierozwiązywalne problemy w miejscach, w których wszystko, co masz, to wymazany typ. Prawdopodobnie musiałbyś całkowicie usunąć wymazanie, ale to dość skomplikowane.Zdefiniuj jedną metodę bez typu typu
void add(Set ii){}
Możesz podać typ podczas wywoływania metody na podstawie swojego wyboru. Będzie działał dla każdego rodzaju zestawu.
źródło
Możliwe, że kompilator tłumaczy Set (Integer) na Set (Object) w kodzie bajtowym Java. W takim przypadku Set (liczba całkowita) byłby używany tylko w fazie kompilacji do sprawdzania składni.
źródło
Set
. Generics nie istnieją w kodzie bajtów, są cukrem syntaktycznym do rzucania i zapewniają bezpieczeństwo typu kompilacji.Natknąłem się na to, gdy próbowałem napisać coś takiego:
Continuable<T> callAsync(Callable<T> code) {....}
iContinuable<Continuable<T>> callAsync(Callable<Continuable<T>> veryAsyncCode) {...}
stały się dla kompilatora 2 definicjamiContinuable<> callAsync(Callable<> veryAsyncCode) {...}
Kasowanie typu dosłownie oznacza kasowanie informacji o argumentach typu z nazw ogólnych. Jest to BARDZO denerwujące, ale jest to ograniczenie, które będzie obowiązywać przez pewien czas w Javie. W przypadku konstruktorów niewiele można zrobić, na przykład 2 nowe podklasy specjalizujące się w różnych parametrach w konstruktorze. Lub zamiast tego użyj metod inicjowania ... (wirtualnych konstruktorów?) O różnych nazwach ...
w przypadku podobnych metod operacji pomocna byłaby zmiana nazwy, np
Lub z kilkoma bardziej opisowymi nazwami, samodokumentującymi dla przypadków oyu, takich jak
addNames
iaddIndexes
takich.źródło