Warum ist es nicht legal, die folgenden zwei Methoden in derselben Klasse zu haben?
class Test{
void add(Set<Integer> ii){}
void add(Set<String> ss){}
}
Ich bekomme die compilation error
Die Methode add (Set) hat dieselbe Löschaddition (Set) wie eine andere Methode im Typ Test.
Während ich es umgehen kann, habe ich mich gefragt, warum Javac das nicht mag.
Ich kann sehen, dass in vielen Fällen die Logik dieser beiden Methoden sehr ähnlich wäre und durch eine einzige ersetzt werden könnte
public void add(Set<?> set){}
Methode, aber das ist nicht immer der Fall.
Dies ist besonders ärgerlich, wenn Sie zwei haben möchten constructors
, die diese Argumente verwenden, da Sie dann nicht einfach den Namen eines der ändern können constructors
.
List
und weiß nicht, wie ich damit umgehen soll.Antworten:
Diese Regel soll Konflikte in Legacy-Code vermeiden, der weiterhin Rohtypen verwendet.
Hier ist ein Beispiel dafür, warum dies nicht erlaubt war , aus dem JLS. Angenommen, bevor Generika in Java eingeführt wurden, habe ich folgenden Code geschrieben:
Sie erweitern meine Klasse wie folgt:
Nach der Einführung von Generika habe ich beschlossen, meine Bibliothek zu aktualisieren.
Sie sind nicht bereit, Aktualisierungen vorzunehmen, und lassen Ihre
Overrider
Klasse in Ruhe. Um das richtig zu überschreibentoList()
Methode , entschieden die Sprachdesigner, dass ein Rohtyp einem generierten Typ "überschreibungsäquivalent" ist. Dies bedeutet, dass Ihre Methodensignatur formal nicht mehr der Signatur meiner Oberklasse entspricht, Ihre Methode jedoch weiterhin überschreibt.Jetzt vergeht die Zeit und Sie entscheiden, dass Sie bereit sind, Ihre Klasse zu aktualisieren. Aber Sie vermasseln ein wenig und anstatt die vorhandene Rohmethode zu bearbeiten
toList()
, fügen Sie eine neue Methode wie die folgende hinzu:Aufgrund der Überschreibungsäquivalenz von Rohtypen liegen beide Methoden in einer gültigen Form vor, um die
toList(Collection<T>)
Methode zu überschreiben . Aber natürlich muss der Compiler eine einzelne Methode auflösen. Um diese Mehrdeutigkeit zu beseitigen, dürfen Klassen nicht mehrere Methoden haben, die überschreibungsäquivalent sind, dh mehrere Methoden mit denselben Parametertypen nach dem Löschen.Der Schlüssel ist, dass dies eine Sprachregel ist, die entwickelt wurde, um die Kompatibilität mit altem Code unter Verwendung von Rohtypen aufrechtzuerhalten. Es ist keine Einschränkung, die durch das Löschen von Typparametern erforderlich ist. Da die Methodenauflösung zur Kompilierungszeit erfolgt, wäre das Hinzufügen generischer Typen zur Methodenkennung ausreichend gewesen.
quelle
javac
.List<?>
und einigeenum
, die Sie definieren, übergeben, um den Typ anzugeben. Die typspezifische Logik könnte sich tatsächlich in einer Methode in der Aufzählung befinden. Alternativ möchten Sie möglicherweise zwei verschiedene Klassen anstelle einer Klasse mit zwei verschiedenen Konstruktoren erstellen. Gemeinsame Logik wäre in einer Oberklasse oder einem Hilfsobjekt, an das beide Typen delegieren.Java-Generika verwenden Typlöschung. Das Bit in den spitzen Klammern (
<Integer>
und<String>
) wird entfernt, sodass Sie zwei Methoden mit identischer Signatur erhalten (dieadd(Set)
Sie im Fehler sehen). Dies ist nicht zulässig, da die Laufzeit nicht weiß, welche für jeden Fall verwendet werden soll.Wenn Java jemals generische Generika erhält, können Sie dies tun, aber das ist jetzt wahrscheinlich unwahrscheinlich.
quelle
Dies liegt daran, dass Java-Generika mit Type Erasure implementiert werden .
Ihre Methoden würden zur Kompilierungszeit in Folgendes übersetzt:Die Methodenauflösung erfolgt zur Kompilierungszeit und berücksichtigt keine Typparameter. ( siehe ericksons antwort )
Beide Methoden haben dieselbe Signatur ohne die Typparameter, daher der Fehler.
quelle
Das Problem ist, dass
Set<Integer>
undSet<String>
tatsächlich alsSet
von der JVM behandelt werden. Die Auswahl eines Typs für die Menge (in Ihrem Fall String oder Integer) ist nur syntaktischer Zucker, der vom Compiler verwendet wird. Die JVM kann nicht zwischenSet<String>
und unterscheidenSet<Integer>
.quelle
Set
Da die Methodenauflösung jedoch zur Kompilierungszeit erfolgt und die erforderlichen Informationen verfügbar sind, ist dies nicht relevant. Das Problem besteht darin, dass das Zulassen dieser Überladungen mit der Berücksichtigung von Rohtypen in Konflikt steht, sodass sie in der Java-Syntax unzulässig gemacht wurden.(Ljava/util/Collection;)Ljava/util/List;
dies nicht funktioniert. Sie könnten verwenden(Ljava/util/Collection<String>;)Ljava/util/List<String>;
, aber das ist eine inkompatible Änderung, und Sie würden an Stellen, an denen Sie nur einen gelöschten Typ haben, auf unlösbare Probleme stoßen. Sie müssten die Löschung wahrscheinlich vollständig fallen lassen, aber das ist ziemlich kompliziert.Definieren Sie eine einzelne Methode ohne Typ wie
void add(Set ii){}
Sie können den Typ beim Aufrufen der Methode basierend auf Ihrer Wahl angeben. Es funktioniert für jede Art von Set.
quelle
Möglicherweise übersetzt der Compiler Set (Integer) in Set (Object) in Java-Bytecode. In diesem Fall wird Set (Integer) nur in der Kompilierungsphase für die Syntaxprüfung verwendet.
quelle
Set
. Generika sind im Bytecode nicht vorhanden, sie sind syntaktischer Zucker für das Casting und bieten Sicherheit beim Kompilieren.Ich bin darauf gestoßen, als ich versucht habe, etwas zu schreiben wie:
Continuable<T> callAsync(Callable<T> code) {....}
undContinuable<Continuable<T>> callAsync(Callable<Continuable<T>> veryAsyncCode) {...}
sie werden für den Compiler die 2 Definitionen vonContinuable<> callAsync(Callable<> veryAsyncCode) {...}
Das Löschen von Typen bedeutet wörtlich das Löschen von Informationen zu Typargumenten aus Generika. Dies ist SEHR ärgerlich, aber dies ist eine Einschränkung, die für eine Weile bei Java bestehen bleibt. Für Konstruktoren kann nicht viel getan werden, zum Beispiel 2 neue Unterklassen, die auf unterschiedliche Parameter im Konstruktor spezialisiert sind. Oder verwenden Sie stattdessen Initialisierungsmethoden ... (virtuelle Konstruktoren?) Mit unterschiedlichen Namen ...
für ähnliche Betriebsmethoden würde das Umbenennen helfen, wie
Oder mit einigen aussagekräftigeren Namen, die sich für Oyu-Fälle wie
addNames
undaddIndexes
oder so selbst dokumentieren .quelle