2015-12-16 4 views
9

Angenommen, haben wir die folgende generische KlasseWelche der überladenen Methoden werden zur Laufzeit aufgerufen, wenn wir Typ löschen anwenden, und warum?

public class SomeType<T> { 
    public <E> void test(Collection<E> collection){ 
     System.out.println("1st method"); 

     for (E e : collection){ 
      System.out.println(e); 
     } 
    } 

    public void test(List<Integer> integerList){ 
     System.out.println("2nd method"); 

     for (Integer integer : integerList){ 
      System.out.println(integer); 
     } 
    } 

} 

nun im Inneren des Hauptverfahrens haben wir die folgenden Code

SomeType someType = new SomeType(); 
List<String> list = Arrays.asList("value"); 
someType.test(list); 

Als Ergebnis der Ausführung someType.test(list) Schnipsel werden wir „2. Methode“ in unserer Konsole bekommen als sowie java.lang.ClassCastException. Wie ich verstehe, ist der Grund, warum zweite test Methode ausgeführt wird, dass wir keine Generika für SomeType verwenden. Der Compiler entfernt also sofort alle Generika-Informationen aus der Klasse (d. H. Sowohl <T> als auch <E>). Nach dieser zweiten test Methode wird List integerList als Parameter haben und natürlich List passt besser zu List als zu Collection.

Betrachten wir nun, dass im Inneren des Hauptverfahrens haben wir den folgenden Code-Snippet

SomeType<?> someType = new SomeType<>(); 
List<String> list = Arrays.asList("value"); 
someType.test(list); 

In diesem Fall werden wir „erste Methode“ in der Konsole. Es bedeutet, dass die erste Testmethode ausgeführt wird. Die Frage ist warum?

Von meinem Verständnis zur Laufzeit haben wir nie Generika-Informationen wegen Typ löschen. Also, warum dann zweite test Methode kann nicht ausgeführt werden. Für mich zweite test Methode sollte (zur Laufzeit) in der folgenden Form sein public void test(List<Integer> integerList){...} Ist es nicht?

+3

Wir haben keine Generika-Informationen zur Laufzeit, aber die Methodenwahl wird zur Laufzeit nicht getroffen. – user2357112

+0

Ok, aber wie ist die Methodenwahl? Gibt es bestimmte Informationen im Bytecode, die jvm mitteilen, welche Methode aufgerufen werden soll? – ruvinbsu

+1

@ruvinbsu, da der Compiler entscheidet, welche Methode er aufrufen sollte, ja. – SomeJavaGuy

Antwort

3

The JLS is a bit of a rat's nest on this one, aber es ist eine informelle (ihre Worte, nicht mein), die Sie Regel verwenden können:

[O] ne Methode ist als ein andere spezifischere, wenn ein durch das erste Verfahren behandelt Aufruf sein könnte ohne Kompilierungsfehler an den anderen übergeben.

Aus Gründen der Argumentation, lassen Sie uns <E> test(Collection<E>) Methode 1 aufrufen und test(List<Integer>) Methode 2.

Lasst uns einen Schraubenschlüssel in hier werfen - wir wissen, dass diese ganze Klasse ist generisch, so Instanziierung es ohne einen Typ von einige Art produziert ... weniger als wünschenswert Typ prüft zur Laufzeit.

der andere Teil dies auf die Tatsache zurückzuführen ist, dass List als Collection spezielleren ist, wenn ein Verfahren zur Herstellung ein List geben wird, wird er versuchen, die mehr als eine mit dem Vorbehalt Collection, leicht unterzubringen, die der Typ sein sollte Zur Kompilierzeit überprüft. Da es ist nicht mit diesem rohen Typ, glaube ich, dass diese besondere Prüfung übersprungen wird, und Java behandelt List<Integer> als spezifischer als .

Sie sollten einen Fehler mit der JVM einreichen, da dies inkonsistent zu sein scheint. Oder zumindest haben die Leute, die die JLS geschrieben haben, erklärt, warum das in etwas besserem Englisch legal ist als ihre wackelige mathematische Notation ...

Weitergehen; Mit Ihrem zweiten Beispiel geben Sie uns die Höflichkeit, Ihre Instanz als Platzhalter zu verwenden, was es Java ermöglicht, die korrekte Kompilierungs-Assertion zu erstellen, die test(Collection<E>) die sicherste-Methode ist, zu wählen.

Beachten Sie, dass keine dieser Prüfungen zur Laufzeit stattfindet. Dies sind alle Entscheidungen, die vor der Ausführung von Java getroffen werden, da mehrdeutige Methodenaufrufe oder ein Aufruf einer Methode mit einem nicht unterstützten Parameter zu einem Kompilierungszeitfehler führt.

Moral der Geschichte: nicht rohe Typen verwenden. Sie sind böse. Es führt dazu, dass sich das Typsystem auf seltsame Weise verhält, und es ist wirklich nur dort, um die Rückwärtskompatibilität aufrechtzuerhalten.

+0

Was ich jetzt verstehe ist, dass der Compiler nicht nur nach Fehlern/Fehlern sucht und Bytecode erzeugt. Es liefert auch einige spezifische Informationen darüber, welche Methode in der Zukunft aufgerufen werden soll. Es bedeutet also, dass der Compiler nicht derjenige ist, der einfach Quellcode übersetzt, sondern auch Entscheidungen für die Zukunft trifft. – ruvinbsu

4

Anwendbare Methoden sind vor Typ Löschung (see JSL 15.12.2.3) abgestimmt. (Erasure bedeutet, dass Laufzeittypen nicht parametrisiert sind, aber das Verfahren wurde bei der Kompilierung ausgewählt, wenn Typ-Parameter verfügbar waren)

Die Art der list ist List<String> daher:

  • test(Collection<E>) ist anwendbar da List<Integer> ist kompatibel mit Collection<E>, wo E ist Integer (formal, die Beschränkungs Formel List<Integer> → Collection<E> [E:=Integer] zu true reduziert, weil ein Subtyp von List<Integer>ist).

  • test(List<String>) ist nicht anwendbar, da List<String> nicht kompatibel ist mit List<Integer> (formal, die Formel Einschränkung List<String>List<Integer> reduziert sich auf false weil String kein übergeordneter Typ von Integer ist).

Die Details sind in JSL 18.5.1 versteckt erklärt.

Für test(Collection<E>):

Lassen θ die Substitution [E: = Integer]

[...]

Ein Satz von Bedingungsformeln, C, ist wie folgt aufgebaut: seien F1, ..., Fn die formalen Parametertypen von m, und seien e1, ..., ek die eigentlichen Argumentausdrücke des Aufrufs.

Dann

In diesem Fall haben wir F1 = Collection<E> und e1 = List<Integer> haben: [der Satz von Bedingungsformeln] enthält

In diesem Fall haben wir List<Integer> → Collection<E> [E:=Integer] (wobei → bedeutet, dass e1 mit F1 kompatibel ist, nachdem die Typvariable E abgeleitet wurde)

Für test(List<String>), es gibt keine Substitution (weil es keine Inferenzvariablen gibt) und die Constraint ist nur List<String>List<Integer>.

Verwandte Themen