2015-04-25 16 views
9

Ich lese über Typ-Inferenz für Generika, und dieser Code wurde als ein Beispiel zur Verfügung gestellt, das nicht kompiliert werden kann.Java Generics, Typ Inferenz, Vererbung?

import java.io.*; 
class LastError<T> { 
    private T lastError; 

    void setError(T t){ 
     lastError = t; 
     System.out.println("LastError: setError"); 
    } 
} 

class StrLastError<S extends CharSequence> extends LastError<String>{ 
    public StrLastError(S s) { 
    } 
    void setError(S s){ 
     System.out.println("StrLastError: setError"); 
    } 
} 
class Test { 
    public static void main(String []args) { 
     StrLastError<String> err = new StrLastError<String>("Error"); 
     err.setError("Last error"); 
    } 
} 

Und die Erklärung in dem Buch war:

„(Es sieht aus wie die setError() Methode in StrLastError ist setError() in der LastError Klasse überschreibt Es ist jedoch nicht der Fall ist, an die.. Zeitpunkt der Kompilierung, das Wissen vom Typ S ist nicht verfügbar.Der Compiler zeichnet daher die Signaturen dieser beiden Methoden als setError(String) in Superklasse und setError(S_extends_CharSequence) in Unterklasse-behandelt sie als überladene Methoden (nicht überschrieben). In diesem Fall, wenn der Aufruf zu setError() gefunden wird, der Compiler findet sowohl den überladenen Methoden Matching, in den vieldeutigen Methodenaufruf Fehlern führte.“

Ich verstehe wirklich nicht, warum Typ S kann nicht bei der Kompilierung abgeleitet werden. String ist bestanden, wenn der Konstruktor der Klasse aufgerufen werden StrLastError, und von der API-Dokumentation, tut String Schnittstelle implementieren CharSequence, ist so nicht gemeint, dass S für <S extends CharSequence> tatsächlich vom Typ String?

Ich habe das Java Online-Tutorial zum Thema Generics mehrmals gelesen. Ich habe "Type Inference" und Inheritance überprüft, ich weiß einfach nicht, wie das Ganze funktioniert. Ich brauche wirklich eine Erklärung zu dieser Frage.

Die Punkte Ich bin stecken in sind:

  1. Wenn der Subtyp nicht S entscheiden kann, wie kommt der Super-Typ T entscheiden kann, weil die übergeordnete Klasse gebunden haben keine obere? Oder wird daraus abgeleitet, dass TString ist, weil der Subtyp zuerst den Konstruktor des Supertyps aufruft?
  2. Ich verstehe, dass, wenn der Konstruktor wie aufgerufen wird:

    StrLastError<CharSequence> err = newStrLastError<>((CharSequence)"Error"); 
    

    wird es keine Zweideutigkeit, da es dann zwingende Ebene Methode ist. (Oder bin ich hier auch falsch?)

Jedoch, wie ich am Anfang gesagt, wenn String übergeben wird, warum nicht S abgeleitet wird String sein?

+0

Warum sagen Sie, dass es keinen Kompilierfehler erzeugt? – user43968

+0

Hallo, ich sagte, es sollte kompilieren Fehler in der ersten Zeile meines Posts erzeugen :) –

+0

Yeah nach der Bearbeitung;) bevor es war "der Code sollte am Ende kompilieren Fehler" – user43968

Antwort

2

Wenn der Subtyp S nicht entscheiden kann, wie kommt der Super-Typ T entscheiden kann, weil die übergeordnete Klasse nicht eine obere Grenze hat ? Oder schließt es, dass T String ist, weil der Subtyp zuerst den Konstruktor des Supertyps aufruft?

Der Super-Typ entscheidet nicht oder folgert, was T ist; Sie ausdrücklich sagen es, was T diese Erklärung ist:

class StrLastError<S extends CharSequence> extends LastError<String> 

T jetzt String für LastError gebunden ist, die jeden Bezug auf T in der übergeordneten Klasse ein konkretes String macht.

Ihre untergeordnete Klasse verfügt jetzt über eine gebundene S extends CharSequence, die jedoch unabhängig von den an die übergeordnete Klasse gebundenen Grenzen ist.

Was jetzt passiert, ist, dass Java Ihre untergeordnete Klasse kompiliert, und das Ergebnis Ihrer untergeordneten Klasse ist, dass zwei Methoden mit einer Signatur, die String entspricht, erstellt werden. (Key Note hier: A String is-a CharSequence.)

In Ihrem Kind Klasse wird setError(Ljava/lang/CharSequence;)V als Signatur erzeugt für setError. Aufgrund der Art, wie Generika funktionieren, wird LastError#setError behandelt, als ob es eine Signatur von setError(Ljava/lang/String;)V hat. Dies ist auch der Grund, warum Sie, wenn Sie die Methode überschreiben, einen String Typ als Parameter anstelle von irgendetwas anderem platzieren.

Also, was erreichen wir sind two methods that have override-equivalent signatures.

void setError(CharSequence s) 
void setError(String s) 

JLS 8.4.8.4. gilt hier.

Es ist möglich, dass eine Klasse mehrere Methoden mit Override-äquivalenten Signaturen erbt (§8.4.2). Dies kann passieren, wenn eine übergeordnete Klasse ist generisch, und es hat zwei Methoden erbt

Es ist ein Fehler bei der Kompilierung, wenn eine Klasse C ein konkretes Verfahren dessen Unterschrift ist eine Untersignatur eines anderen konkreten Verfahrens geerbt von C. Diese waren in der generischen Deklaration unterschiedlich, aber haben die gleiche Signatur in dem bestimmten Aufruf verwendet.


Ich verstehe, dass, wenn der Konstruktor als StrLastError err = new StrLastError aufgerufen <> ((CharSequence) "Error"); es wird keine Zweideutigkeit, da seine schlichte Methode dann überschreiben. (Oder ich bin auch hier falsch)

Nein, jetzt bist du mit Messing raw types. Interessanterweise wird es Arbeit, in erster Linie, weil die Unterschriften für die beiden Methoden worden:

void setError(Object s) 
void setError(String s) 

Sie wollen Generika verwenden, um ein Szenario wie diese zu vermeiden; Vielleicht möchten Sie die Super-Klassen-Methode irgendwann aufrufen, aber in diesem Szenario mit diesen Bindungen ist es sehr schwer zu erreichen.

+0

Danke, ich glaube, ich habe eine in meinem 2. Punkt verpasst, werde den Beitrag überarbeiten, aber ich verstehe immer noch nicht, warum, wenn explizit in der Deklaration im Haupt verwendet wird, kann S nicht abgeleitet werden Kompilierzeit –

+0

Der generische Typ, der an das Kind gebunden ist, stimmt nicht mit dem Typ überein, der an den Eltern gebunden ist. Heck, wenn Sie einen Typ-Parameter für eine generische Methode hätten, wären diese beiden Typen auch nicht identisch. Wenn Sie neugierig auf diese Art von Verhalten sind (es geht hier etwas aus dem Rahmen), fühlen Sie sich ermutigt, eine neue Frage zu stellen. – Makoto

+0

Also kompilierte der Compiler zuerst die Klasse "LastError", dann "StrLastError", und im Hauptteil des Körpers, als der Konstruktor aufgerufen wurde, wurde LastError explizit gesagt, dass T String aus der Deklaration des Subtyps sein sollte, und den Typparameter der Unterklasse belassen S unbestimmt? –

-2

Dies ist ein heikles Beispiel, um es zu beheben, müssen Sie die folgende Zeile ändern:

class StrLastError<S extends CharSequence> extends LastError<S> 
+2

Ich denke, Sie haben Recht mit der Lösung, aber das Beispiel wurde absichtlich falsch gesagt und das OP möchte verstehen, warum es falsch ist. – paisanco

4

Sie haben sich daran zu erinnern, dass die Klassen einzeln kompiliert werden. Java Generics sind keine Templates wie in anderen Sprachen. Es gibt nur eine kompilierte Klasse und nicht eine Klasse pro Typ, mit dem sie verwendet wird.

Auf diese Weise können Sie sehen, dass die Klasse StrLastError so kompiliert werden muss, dass sie auch mit anderen Klassen verwendet werden kann, die CharSequence als generischen Typ S implementieren.

Das ist der Grund, warum der Compiler Sie zwei verschiedene Methoden anstelle einer überschriebenen erhält. Jetzt wäre es ein Laufzeit-Job, zu sehen, dass die Unterklasse die Methode im Elternteil möglicherweise gerade in den Fällen überschreiben wollte, in denen die Typen dies vorschlagen. Da dieses Verhalten für den Entwickler schwer zu verstehen ist und möglicherweise zu Programmierfehlern führen würde, gibt es eine Ausnahme.

Wenn Sie CharSequence als gattungs Parameter der Klasse StrLastError, verwenden Sie die setError Methode in der übergeordneten Klasse aufrufen, da die Art der "Last Error"String ist, die als CharSequence spezifischere und Java wählt immer die spezifischen Methode für den Fall, dass es überlastet ist. (Ich hoffe, es ist klar, das die Methode nicht entweder in diesem Fall außer Kraft gesetzt wurde)