2017-06-17 2 views
0

Ich verstehe ziemlich die Generics. Meiner Ansicht nach müssen Sie Type-Parameter angeben, wenn Sie eine generische Klasse oder Methode aufrufen. Zum Beispiel:Typ Cast zu Generic

List<String> strings = new ArrayList<String>(); 

Aber ich habe einige Schwierigkeiten, folgenden Code zu verstehen.

public static <T extends Employee> T findById(@NonNull factory, int employeeId) { 
    return (T) factory.findEmployeeById(employeeId); 
} 

Mitarbeiter hat viele Subtypen. Zum Beispiel:

public class Programmer extends Employee {} 

public class Engineer extends Employee {} 

Durch die Nutzung dieser findById Methode kann ich erfolgreich wie die folgenden implementieren.

Programmer employee = EmployeeUtils.findById(factory, 2); 

Bei dem obigen Verfahren von findById, wie würde jemand wissen, was die Art der T ist? Es gibt nur einen Hinweis darauf, dass es sich um einen Subtyp von Employee handelt. Es kann nicht wissen, ob es ein Programmierer oder Ingenieur ist. Wie kann der Java-Compiler den Typ, der zur Laufzeit in die konkrete Unterklasse (Programmer oder Engineer) umgewandelt wird, erfolgreich ausführen?

Bitte leiten Sie mich weiter, wenn Sie Java Generics weiterempfehlen möchten.

+2

"Aber ich habe einige Schwierigkeiten, folgenden Code zu verstehen", das könnte sein, weil dieser Code eine allgemeine aber missbräuchliche Verwendung von Generika ist: es ist nicht Typ sicher. –

Antwort

1

How can Java compiler successfully do the type cast at the runtime to Programmer (or Engineer)?

Der Java-Compiler Abgüsse nicht tun.

Eine Besetzung ist einfach eine Art, dem Compiler zu sagen: "Ich weiß mehr als du; vertrau mir." Der Compiler wird immer noch versuchen, Sie davon abzuhalten, Casts zu erstellen, die definitiv unsicher sind (wie zum Beispiel ein String zu einem Integer Casting), aber durch Casting übernehmen Sie die Verantwortung für die Typsicherheit weg vom Compiler. Sie sollten nur dann casten, wenn Sie durch die Semantik Ihres Codes/Systems wissen, dass es sicher ist.

Dieses spezielle Muster ist für das Abrufen heterogener Entitäten aus einem Repository üblich. aber es ist nicht typsicher.

Die Aufrufer der Methode ist es nur die Methode mit der "richtigen Art von ID" aufzurufen. Das Problem besteht darin, dass der Code nicht angibt, dass er fehlschlagen könnte.

Der Grund, warum dieses Muster mich immer ärgert, ist, dass es den tatsächlichen Ort verbirgt, an dem das Problem auftritt. Erinnern Sie sich daran, dass Java-Generika im Grunde genommen nur eine Auswahl von Casts sind. Dies bedeutet, dass die Methode „wirklich“ sieht wie folgt aus:

public static Employee findById(@NonNull factory,int employeeId) { 
    // There is actually no cast here! 
    return factory.findEmployeeById(employeeId); 
} 

Und der Anruf Seite sieht wie folgt aus:

// The cast is here instead! 
SpecificEmployeeType e = (SpecificEmployeeType) findById(someId); 

(Versuchen Sie schreiben den Code explizit wie diese, und vergleichen Sie den Bytecode mit dem generischen Version)

Als solche, während Sie die Warnung in der findById Methode unterdrücken können, tritt die tatsächliche Ausnahme an der Aufruf-Site. Also ist die Warnung - die Anzeige, dass dort ein Problem auftreten kann - an der falschen Stelle.

Wenn Sie es explizit ohne die Generika schreiben, können Sie genau sehen, wo das Problem tatsächlich auftritt.

Die Leute sagen oft, dass sie Generika auf diese Weise verwenden wollen, weil es "sauberer" ist: Sie brauchen nicht die explizite Umwandlung oder Unterdrückung an jeder Call-Site. Persönlich möchte ich wollen, die extra Zeug dort: es macht es so aussehen, als gäbe es etwas Gefährliches dort (was gibt es!) Und damit Sie wissen, dass Sie besonders vorsichtig sein müssen.


Es ist auch erwähnenswert, dass Sie nicht@SuppressWarnings("unchecked"), um Ihren Code hinzufügen - entweder in der findById Methode oder an den Aufrufstellen - wenn Sie absolut sicher sein können, dass die Besetzung sicher ist.

Wie das Casting ist die Unterdrückung von Warnungen eine Möglichkeit, Verantwortung für Dinge zu übernehmen, die der Compiler nicht nachweisen kann. Indem Sie Warnungen natürlich unterdrücken, ignorieren Sie nur die Hilfe, die der Compiler zu bieten versucht. Wie bei jedem Vorsichtshinweis steht es Ihnen frei, dies zu ignorieren, aber es ist tollkühn, dies zu tun, ohne die Konsequenzen vollständig zu verstehen.

1

Der Compiler überprüft in diesem Fall nicht die Art des Cast. Sie sollten eine Warnung erhalten, in der Sie über eine ungeprüfte Umwandlung zu T informiert werden, wenn Ihre Compiler-Optionen entsprechend eingestellt sind.

Die Prüfung wird zur Laufzeit ausgeführt. Betrachten Sie den folgenden Code ein:

public class GenericTest { 
    public abstract static class Employee {} 

    public static class Programmer extends Employee {} 

    public static class Engineer extends Employee {} 

    public static void main(String[] args) { 
     Programmer p = getEmployee(); 
    } 

    public static <T extends Employee> T getEmployee() { 
     return (T) new Engineer(); 
    } 
} 

Dieser Code kompiliert mit einer Warnung Type safety: Unchecked cast from GenericTest.Engineer to T. Zur Laufzeit eines Classcast auftreten:

java.lang.ClassCastException: GenericTest$Engineer cannot be cast to GenericTest$Programmer

Wenn Sie diese Besetzung zu tun, sollten Sie sicher sein, dass der Typ zur Laufzeit korrekt ist, sonst hässlich Ausnahmen fliegen.

Generika werden zur Kompilierzeit typisiert gelöscht. Wenn Sie also den kompilierten Bytecode analysieren, sehen Sie, dass die Methode entsprechend ihrer Signatur Employee zurückgibt. Tatsächlich ist die kompilierten Bytecode von obigen Beispiel ist fast identisch mit dem Bytecode aus dem folgenden Code kompiliert (mit den gleichen Klassen):

public static void main(String[] args) { 
    Programmer p = (Programmer) getEmployee(); 
} 

public static Employee getEmployee() { 
    return new Engineer(); 
} 

Hoffentlich wird Ihnen helfen, zu verstehen, was zur Laufzeit geschieht.

Weiterführende Literatur betrachten die Oracle Tutorial for Generics

+0

Ich habe eine Beschriftung für die Überschreibung hinzugefügt, um die unkontrollierte Umwandlung auszublenden. Kannst du mehr über das, was T sein wird, in der Rückkehrerklärung erklären? –

+0

@ Steve.NayLinAung Ich habe meine Antwort aktualisiert. Hoffe das wird dir helfen – SilverNak

0

Der zurückkehrende Typ T lautet Mitarbeiter. Der folgende Code bestätigt dies.

public class GenericTest { 
    public abstract static class Employee { 
    } 

    public static class Programmer extends Employee { 
    } 

    public static class Engineer extends Employee { 
     void testMethod(){ 
      System.out.println("Engineer method"); 
     } 
    } 

    public static void main(String[] args) { 
     getEmployee().testMethod(); 
    } 

    public static <T extends Employee> T getEmployee() { 
     return (T) new Engineer(); 
    } 
} 

Wenn wir versuchen, den Code auszuführen, erhalten wir einen Kompilierungsfehler

Error:(28, 22) java: cannot find symbol symbol: method engineerMethod() location: class GenericTest.Employee

Um den Fehler zu beheben, müssen Sie eine „abstrakte Methode“ oder „Methode“ der abstrakten Klasse hinzuzufügen, wie dies:

public abstract static class Employee { 
    void testMethod(){}; 
}