2010-08-10 3 views
9

Ich habe die folgende Testklasse, die Generics verwendet, um eine Methode zu überladen. Es funktioniert, wenn es mit javac kompiliert wird und kompiliert nicht in Eclipse Helios. Meine Java-Version ist 1.6.0_21.Java-Generics-Code kompiliert mit javac, schlägt mit Eclipse fehl Helios

Alle Artikel, die ich lese, zeigen an, dass Eclipse richtig ist und dieser Code sollte nicht funktionieren. Wenn jedoch mit javac und run kompiliert wird, wird die richtige Methode ausgewählt.

Wie ist das möglich?

Danke!

import java.util.ArrayList; 

public class Test { 
    public static void main (String [] args) { 
     Test t = new Test(); 
     ArrayList<String> ss = new ArrayList<String>(); 
     ss.add("hello"); 
     ss.add("world"); 
     ArrayList<Integer> is = new ArrayList<Integer>(); 
     is.add(1); 
     is.add(2); 
     System.out.println(t.getFirst(ss)); 
     System.out.println(t.getFirst(is)); 
    } 
    public String getFirst (ArrayList<String> ss) { 
     return ss.get(0); 
    } 
    public Integer getFirst (ArrayList<Integer> ss) { 
     return ss.get(0); 
    } 
} 
+4

Wenn dies ein tatsächlicher Anwendungsfall ist, würde ich nur eine Methode empfehlen 'public A getFirst (ArrayList ss) {return ss.get (0); } '... wenn es nur zu Illustrationszwecken erfunden wurde, mach weiter –

+1

Können Sie die Artikel zitieren? – trashgod

Antwort

2

Es wäre möglich, wenn Javac einen Fehler darin hätte. Javac ist nur Software und ist genauso anfällig für Bugs wie jede andere Software.

Plus, die die Programmiersprache Java Spec ist sehr komplex und ein wenig vage an Orten, so dass es auch zwischen den Eclipse-Typen und den javac Jungs bis auf einen Unterschied in der Interpretation sein könnte.

Ich würde damit beginnen, dies auf den Eclipse-Support-Kanälen zu fragen. Sie sind normalerweise sehr gut darin, diese Dinge aufzugreifen und zu erklären, warum sie denken, dass sie recht haben oder zugeben, dass sie falsch liegen.

Für den Rekord, ich denke, Eclipse ist auch hier richtig.

+0

Eclipse ist falsch. Das ist absolut legales Java. – erickson

+0

Meh. Ich habe nur auf historischen Durchschnittswerten geschätzt. Angesichts der Analyse anderer Leute vermute ich, dass du recht hast. Wie auch immer, mit den Eclipse-Jungs ist das der Weg nach vorne. – dty

2

Sind Sie sicher, dass Eclipse auch Java 1.6 verwenden soll?

+2

Aus Interesse, warum denkst du das ist relevant? Eclipse ist auf mindestens 1.5 eingestellt oder würde überhaupt nicht kompilieren. Glauben Sie, dass es einen signifikanten Unterschied zwischen den Semantiken in 1.5 und 1.6 gibt? – dty

+0

Ich wollte nur sicherstellen, dass es nicht auf 1.4 gesetzt wurde. Ich verwende Eclipse nicht, aber ich weiß, dass IDEs in der Regel eine Java-Version haben, die unabhängig von Ihrem System ist. Soweit 1.5-1.6 Unterschiede, ich kenne keine signifikanten. – Jeffrey

+0

Wenn es auf 1,4 gesetzt wurde, hätte es aufgrund der Generika überhaupt nicht kompiliert. – dty

0

Eclipse und JavaC verwenden verschiedene Compiler. Eclipse verwendet einen Compiler von Drittanbietern, um Ihren Code in Bytecodes für die Java VM umzuwandeln. Javac verwendet den Java-Compiler, den Sun veröffentlicht. Daher ist es möglich, dass identischer Code leicht unterschiedliche Ergebnisse liefert. Netbeans, glaube ich, benutzt Suns Compiler, also checkt es auch dort ein.

+2

Ein wenig los mit der Sprache hier. Um es klar zu sagen, ist Java der Compiler, den Sun (Oracle) veröffentlicht. Und Eclipse verwendet keinen Compiler von Drittanbietern - es verwendet eine eigene Compiler-Implementierung. – dty

3

Funktioniert für mich in Eclipse Helios. Die Methodenauswahl erfolgt zur Kompilierzeit, und der Compiler verfügt über genügend Informationen dazu.

4

Dieser Code ist korrekt, wie in JLS 15.12.2.5 Choosing the Most Specific Method beschrieben.

Bedenken Sie auch Codierung an die Schnittstelle:

List<String> ss = new ArrayList<String>(); 
List<Integer> is = new ArrayList<Integer>(); 
// etc. 

Als @McDowell Notizen erscheinen die modifizierte Methodensignaturen in der Klassendatei:

$ javap build/classes/Test 
Compiled from "Test.java" 
public class Test extends java.lang.Object{ 
    public Test(); 
    public static void main(java.lang.String[]); 
    public java.lang.String getFirst(java.util.ArrayList); 
    public java.lang.Integer getFirst(java.util.ArrayList); 
} 

Beachten Sie, dass dies nicht @ meriton Beobachtung widerspricht über die Klassendatei. Zum Beispiel

die Ausgabe dieses Fragment
Method[] methods = Test.class.getDeclaredMethods(); 
for (Method m : methods) { 
    System.out.println(Arrays.toString(m.getGenericParameterTypes())); 
} 

zeigt die formalen Parameter von main(), sowie die zwei generische Typparameter:

[class [Ljava.lang.String;] 
[java.util.ArrayList<java.lang.String>] 
[java.util.ArrayList<java.lang.Integer>] 
+1

Nach dem Löschen des Typs werden die Methodensignaturen zu 'public String getFirst (ArrayList)' und 'public Integer getFirst (ArrayList)'. – McDowell

+1

Kein McDowell, Methodensignaturen werden nicht gelöscht. – meriton

+0

@meriton: @McDowell: IIUC, Sie sind beide corect: die Signaturen werden gelöscht _und_ die generischen Typparameter werden beibehalten, wie oben vorgeschlagen. – trashgod

0

Ein Punkt im Auge zu behalten ist, dass (alle ?, sicherlich einige, wie zum Beispiel der NetBeans-Editor tut dies) statische Code-Analyse-Tools nicht Rückgabetyp oder Modifikatoren (private/öffentliche etc.) als Teil der Methode Unterschrift.

Wenn das der Fall ist, dann würden mit Hilfe des Typs Löschung beide getFirst Methoden die Signatur getFirst(java.util.ArrayList) erhalten und damit einen Namenskonflikt auslösen ...

+0

Generics werden in * Bytecode * (dem Befehlssatz der Java Virtual Machine) gelöscht. Methodensignaturen sind nicht Teil des Anweisungssatzes; Sie werden wie im Quellcode angegeben in die Klassendatei geschrieben. Weitere Informationen finden Sie in meiner Antwort. – meriton

6

The Java Language Specification, section 8.4.2 schreibt:

Es ist ein Fehler Übersetzungszeit zwei Methoden mit Überschreibung äquivalenten Signaturen (unten definiert) in einer Klasse zu erklären.

Zwei Methodensignaturen m1 und m2 sind override-äquivalent, wenn entweder m1 eine Untersignatur von m2 oder m2 eine Untersignatur von m1 ist.

Die Signatur eines Verfahrens M1 ist ein Untersignatur der Signatur einer Methode m2, wenn entweder

  • m2 die gleiche Signatur wie M1 oder

  • die Signatur von M1 hat die gleiche wie das Löschen der Unterschrift von m2.

Verständlich sind die Verfahren nicht äquivalent zu überschreiben, da ArrayList<String> nicht ArrayList ist (die Löschung von ArrayList<Integer>).

Also die Methoden zu deklarieren ist legal. Außerdem ist der Methodenaufrufausdruck gültig, da dort eine spezifischste Methode trivial ist, da es nur eine Methode gibt, die den Argumenttypen entspricht.

Bearbeiten: Yishai weist richtig darauf hin, dass in diesem Fall eine andere Einschränkung eng umgangen wird. Die Java Language Specification, section 8.4.8.3 schreibt:

Es ist ein Kompilierung Fehler ist, wenn eine Typdeklaration T ein Element Methode m1 und es existiert ein Verfahren m2 erklärte in T oder einen übergeordneten Typen von T, so hat, dass alle der folgenden Bedingungen halten:

  • m1 und m2 haben den gleichen Namen.
  • m2 ist zugänglich von T.
  • Die Unterschrift von m1 ist keine Unterschrift (§8.4.2) der Unterschrift von m2.
  • m1 oder eine Methode m1 überschreibt (direkt oder indirekt) das gleiche Löschen wie m2 oder einige Methoden m2-Überschreibungen (direkt oder indirekt).

Anhang: Auf ersure, und das Fehlen davon

Entgegen der weit verbreiteten Vorstellung, Generika in Methodensignaturen werden nicht gelöscht. Generics werden in Bytecode (der Befehlssatz der Java Virtual Machine) gelöscht. Methodensignaturen sind nicht Teil des Anweisungssatzes; Sie werden wie im Quellcode angegeben in die Klassendatei geschrieben. (Nebenbei kann diese Information auch zur Laufzeit durch Reflexion abgefragt werden).

Denken Sie daran: Wenn die Typ-Parameter von Klassendateien gelöscht wurden vollständig, wie könnte die Code-Vervollständigung in der IDE Ihrer Wahl Anzeige, dass ArrayList.add(E) einen Parameter vom Typ nimmt E und nicht Object (= die Löschung von E), wenn Sie hatten den JDKs-Quellcode nicht angehängt? Und wie würde der Compiler wissen, einen Kompilierungsfehler zu werfen, wenn der statische Typ des Methodenarguments kein Untertyp von E war?

+3

Ich denke, der Fall ist viel näher als das. Bedenken Sie Folgendes: Wenn Sie die Rückgabetypen beider Methoden in Object ändern, wird der Code nicht mehr kompiliert. Die JLS sagt nicht, dass der Rückgabetyp ein Unterscheidungsmerkmal einer Methodensignatur sein kann, um die Überschreibäquivalenz zu bestimmen. Ich stimme letztendlich zu, dass der Sun-Compiler korrekt ist, aber es ist ein sehr knapper Ruf. – Yishai

+0

Guter Punkt, habe ich bearbeitet, um den relevanten Abschnitt einzuschließen. Bei der Regel handelt es sich nicht um eine Überschreibung der Äquivalenz (bei der Rückgabetypen angegeben sind, die keine Rolle spielt), sondern allgemeiner. – meriton

1

Nach einigen Recherchen habe ich die Antwort:

Der Code, wie es oben angegeben ist, sollte nicht kompiliert werden. ArrayList<String> und ArrayList<Integer>, zur Laufzeit ist immer noch ArrayList. Aber dein Code funktioniert nicht, weil der Typ zurückgibt. Wenn Sie für beide Methoden dieselben Rückgabetypen einstellen, kompiliert javac das nicht ...

Ich habe gelesen, dass es einen Fehler in Java 1.6 (der bereits in Java 1.7 behoben wurde) über diesen Fehler gibt. Alles dreht sich um wiederkehrende Typen ... also müssen Sie die Signatur Ihrer Methoden ändern.

Es gibt die bug 6182950 in Oracle's Bug Database.

Verwandte Themen