2015-02-19 18 views
17

meine Frage ist ziemlich theoretisch ... Dies ist die Unterschrift von Class.asSubclass ist (Javadoc):Class.asSubclass Unterschrift

public <U> Class<? extends U> asSubclass(Class<U> clazz) 

Warum Wildcard Generika in den Rückgabetyp verwendet? Von meinem Verständnis von Generika könnte eine bessere Signatur sein:

public <U> Class<U> asSubclass(Class<U> clazz) 

da kann man sicher Guss

Class<? extends U> 

zu einem einfacher

Class<U> 

Bloch in seinem Buch "Effective Java" empfiehlt (Seite 137, Punkt 28):

D o Verwenden Sie keine Platzhaltertypen als Rückgabetypen. Anstatt Ihren Benutzern zusätzliche Flexibilität zu bieten, werden sie gezwungen, Platzhalter im Client-Code zu verwenden.

Was ist der Grund für diese Wahl? Was ich vermisse? Vielen Dank im Voraus.

Edit: Wie @egelev schon sagt, konnte ich in die Tat meine Frage in einer anderen Art und Weise formuliert habe ... in der Tat der Rückkehr der Eingangsparameter sinnlos wäre „wie es ist“. Das eigentliche Problem ist also: Was ist die wirkliche Nützlichkeit der Class.asSubclass-Methode im Vergleich zu einem normalen Cast? Beide würden eine ClassCastException werfen, wenn Probleme auftreten.

MAYBE wurde hinzugefügt unkontrolliert Gießen in einer bestimmten Situation zu vermeiden: Wenn Sie das Ergebnis der asSubclass Methode übergeben direkt an einem anderen Methode für einen eingeschränkten Typparameter zu fragen, wie hier (von Effective Java, Seite genommen 146):

AnnotatedElement element; 
... 
element.getAnnotation(annotationType.asSubclass(Annotation.class)); 

die Unterzeichnung des oben genannten Verfahrens ist:

<T extends Annotation> T getAnnotation(Class<T> annotationClass); 

Es scheint mir, dass die asSubclass Methode nur ein Weg ist, einen (in der Tat) ungeprüft Guss ohne eine richtige Zusammenarbeit zu tun! mpiler Warnung ...

Und das am Ende wieder vorschlagen, meine erste Frage: die Unterschrift

public <U> Class<U> asSubclass(Class<U> clazz) 

wäre ebenso wirksam sein (auch wenn seltsam, ich gebe es zu)! Es wäre vollständig kompatibel mit dem Beispiel getAnnotation und würde den Client-Code nicht zwingen, sinnlose Platzhalter-Generics zu verwenden.

Edit2: Ich denke, meine allgemeine Frage wurde gelöst; vielen Dank. Wenn jemand andere gute Beispiele über die Korrektheit der asSubclass-Signatur hat, füge sie bitte der Diskussion hinzu. Ich würde gerne ein vollständiges Beispiel sehen, das asSubclass mit meiner Signatur verwendet und offensichtlich nicht funktioniert.

+0

Ähnliche Frage, aber kein Duplikat! http://stackoverflow.com/questions/20661835/what-is-the-point-of-t-extends-someclass – christopher

Antwort

12

Im Falle des Rückgabetyps Class<? extends U>. Läßt zuerst versuchen, das Verständnis, die getClass Signatur:

AbstractList<String> ls = new ArrayList<>(); 
Class<? extends AbstractList> x = ls.getClass(); 

hatte nun der Compiler uns erlaubt zu tun:

Class<AbstractList> x = ls.getClass(); 

Diese falsch gewesen wäre. Denn zur Laufzeit wäre ls.getClassArrayList.class und nicht AbstractList.class. Weder kann ls.getClass Rückkehr Class<ArrayList> weil ls vom Typ AbstractList <> und Arraylist nicht

So ist der Compiler, jetzt sagt - Ok! Ich kann nicht zurückgeben Class<ArrayList> noch kann ich Class<AbstractList> zurückgeben. Aber da ich weiß, ls ist eine AbstractList sicher, so kann das tatsächliche Klassenobjekt nur ein Subtyp von AbstractList sein. So ist Class<? extends AbstractList> eine sehr sichere Wette. Wegen der Wild Card: Sie nicht tun:

AbstractList<String> ls = new ArrayList<>(); 
Class<? extends AbstractList> x = ls.getClass(); 
Class<ArrayList<?>> xx = x; 
Class<AbstractList<?>> xxx = x; 

Die gleiche Logik auf Ihre Frage bezieht.

List<String> ls = new ArrayList<>(); 
Class<? extends List> x = ls.getClass(); 
Class<AbstractList> aClass = x.asSubclass(AbstractList.class); //BIG ISSUE 

Above aClass zur Laufzeit ist Class<Arraylist> und nicht Class<AbstractList>:

public <U> Class<U> asSubClass(Class<U> c) 

Die unten hätte zusammengestellt: wie erklärt wurde, sagen nehme an, es. Also das sollte nicht erlaubt sein !! Class<? extends AbstractList> ist die beste Wette.



Der erste Gedanke, den ich bei der Frage auf der Suche hatten, war, warum nicht wurde es erklärt, wie:

public <U extends T> Class<? extends U> asSubClass(Class<U> c) 

es mehr Sinn macht, eine Kompilierung Beschränkung auf die Argumente zu haben, Ich kann gehen. Aber der Grund, warum ich es nicht bevorzugte, war - das hätte die Rückwärtskompatibilität des Pre-Java5-Codes zerstört. Zum Beispiel würde Code wie unten, der mit pre-Java5 compiliert wurde, nicht mehr kompilieren, wenn es asSubClass wie oben gezeigt deklariert wurde.

Class x = List.class.asSubclass(String.class); //pre java5 
// this would have not compiled if asSubclass was declared above like 

Quick-Check:

public static <T, U extends T> Class<? extends U> asSubClaz(Class<T> t, Class<U> c){ 
     return t.asSubClass(c); 
    } 
    public static <T, U> Class<? extends U> asSubClazOriginal(Class<T> t, Class<U> c){ 
     return t.asSubClass(c); 
    } 
    asSubClazOriginal(List.class, String.class); 
    asSubClaz(List.class, String.class); //error. So would have broken legacy code 

PS: Für die editierte Frage, warum asSubClass statt Besetzung? - Weil Besetzung Verrat ist.Zum Beispiel:

List<String> ls = new ArrayList<>(); 
Class<? extends List> x = ls.getClass(); 
Class<AbstractList> aClass = (Class<AbstractList>) x; 

Oben wäre immer erfolgreich, weil Generika gelöscht werden. Also seine Klasse in eine Klasse umgewandelt. Aber aClass.equals(ArrayList.class) würde falsch geben. Also definitiv Besetzung ist falsch. Falls Sie die Sicherheit geben, dann können Sie asSubClaz oben

+0

@CptWasp Überprüfen Sie 'PS' auf why' asSubClass'. Ich hoffe, die Antwort hat Ihre Fragen geklärt. – Jatin

+0

Ich verstehe immer noch nicht, warum es wirklich typsicher ist. In der Praxis können Sie eine ClassCastException genau wie bei einer manuellen Umwandlung erhalten ... Ich verstehe, dass, was Sie bekommen, IS '', aber für mich ist es das gleiche wie für den Client-Code, und der resultierende Code ist nicht durcheinander Platzhalter – CptWasp

+0

Es tut mir leid, aber Sie haben immer noch nicht meine Antwort bekommen. Es ist nicht sicher, zurückzukehren. Daher kann der Compiler in keiner Weise nur zurückgeben. Lesen Sie für die Typensicherheit den zweiten Teil meiner Antwort, der aus Gründen der Abwärtskompatibilität nicht ganz sicher ist. Aber genug, um die Hälfte der Fehler zu entfernen. – Jatin

2

Die Idee hinter dieser Methode ist genau das Hinzufügen dieses Platzhalters.Soweit ich verstehe, besteht der Zweck dieser Methode darin, das Klassenobjekt this in ein Klassenobjekt umzuwandeln, das eine Unterklasse des Parameters clazz darstellt. Deshalb wird das Ergebnis als Class<? extends U> (die Klasse von etwas, das U erweitert) deklariert. Andernfalls würde es keinen Sinn ergeben, da sein Rückgabetyp genau dem Typ seines einzigen Parameters entspricht, d. H. Er wird nur return clazz; ausgeben. Es führt eine Laufzeittypüberprüfung durch und wandelt den Parameter in Class<? extends U> um (natürlich wird ClassCastException ausgelöst, wenn das Ziel des Aufrufs, d. H. this, nicht die Klasse eines U-Derivats darstellt). Dies ist der bessere Ansatz als der einfache (Class<? extends U>) Casting-Typ, da letzterer den Compiler veranlasst, eine Warnung zu erzeugen.

+2

Vielen Dank, ich stimme zu, dass in der Tat mit meiner Argumentation Sie einfach die asSubclass-Methode ignorieren und eine normale verwenden können , manuelle (ungeprüfte) Umwandlung, um eine Klasse in einen bestimmten Typ zu konvertieren ... Sie benötigen dazu keine Methode. Aber was sollte der Zweck sein, einen Typ auf "Class " einzuschränken? Ich fass es nicht. – CptWasp

7

Ich denke verwenden, dass, um das zu verstehen, muss man zuerst die feinen Unterschiede zwischen Typen und Klassen verstehen: Objekte haben Klassen, haben Referenzen Typen.

Klassen vs Typen

denke ich, ein Beispiel ist ein guter Weg zu erklären, was ich hier meine. Lassen Sie die Existenz der folgenden Klassenhierarchie übernehmen:

class Mammal {} 
class Feline extends Mammal {} 
class Lion extends Feline {} 
class Tiger extends Feline {} 

Dank Polymorphismus Subtyp wir mehrere Verweise auf das gleiche Objekt erklären könnte.

Tiger tiger = new Tiger(); //! 
Feline feline = tiger; 
Mammal mammal = feline; 

Interessanterweise, wenn wir jeder dieser Referenzen gebeten, den Namen ihrer Klasse, die sie alle würden mit der gleichen Antwort antworten: „Tiger“.

System.out.println(tiger.getClass().getName()); //yields Tiger 
System.out.println(feline.getClass().getName()); //yields Tiger 
System.out.println(mammal.getClass().getName()); //yield Tiger 

Das bedeutet die tatsächliche Klasse des Objekts festgelegt ist, ist seine Klasse diejenige, die wir verwenden, um es zu instanziieren, wenn wir die „neuen“ Betreiber in unserem obigen Code verwendet.

Die Referenzen, auf der anderen Seite können verschiedene Typen, polymorph mit der tatsächlichen Klasse von Objekt kompatibel (d. H. Ein Tiger in diesem Fall).

So haben Objekte eine feste Klasse, während Referenzen kompatible Typen haben.

Dies ist eher verwirrend, da die Klassennamen die gleichen sind, die wir verwenden, um die Typen unserer Referenzen zu benennen, aber semantisch gesehen gibt es einen feinen Unterschied, wie wir oben sehen können.

Der vielleicht verwirrendste Teil ist die Erkenntnis, dass Klassen auch Objekte sind, und daher können sie kompatible Referenzen von ihren eigenen haben. Zum Beispiel:

Class<Tiger> tigerClass = null; 
Class<? extends Tiger> someTiger = tigerClass; 
Class<? extends Feline> someFeline = tigerClass; 
Class<? extends Mammal> someMammal = tigerClass; 

In meinem Code über das Objekt verwiesen wird, ist ein Klassenobjekt (was ich für vorerst null links), und diese Referenzen hier haben verschiedene Arten verwendet werden, dass die hypothetische Objekt zu erreichen.

Also, das Wort "Klasse" wird hier verwendet, um einen "Referenztyp" zu nennen, der auf ein tatsächliches Klassenobjekt verweist, dessen Typ mit einer dieser Referenzen kompatibel ist.

In meinem obigen Beispiel konnte ich ein solches Klassenobjekt nicht definieren, da ich die ursprüngliche Variable auf null initialisiert habe. Dies ist beabsichtigt, und in einem Moment werden wir sehen, warum.

Über getClass und getSubclass auf Referenzen aufrufen

Nach @ Jatin das Beispiel, das die getClass Methode betrachtet, jetzt wollen wir das folgende Stück von polymorphen Code betrachten:

Mammal animal = new Tiger(); 

Jetzt wissen wir, dass unabhängig der Tatsache, dass unsere animal Referenz vom Typ Mammal ist, ist die tatsächliche Klasse des Objekts, und wird immer Tiger (dh Klasse) sein.

Was sollen wir bekommen, wenn wir das tun?

? type = mammal.getClass() 

Sollte type ein Class<Mammal> oder ein Class<Tiger> sein?

Nun, das Problem ist, dass, wenn der Compiler sieht eine Referenz vom Typ Mammal, und es kann nicht sagen, was die tatsächliche Klasse des Objekts ist dieser Verweis auf zeigt. Das konnte nur zur Laufzeit festgestellt werden, oder? Es kann tatsächlich ein Säugetier sein, aber es kann auch eine seiner Unterklassen sein, wie ein Tiger, oder?

Also, wenn wir nach seiner Klasse fragen, bekommen wir Class<Mammal> nicht, weil der Compiler nicht sicher sein kann. Stattdessen erhalten wir eine Class<? extends Mammal>, und das macht viel mehr Sinn, denn schließlich weiß der Compiler, dass basierend auf den Regeln des Subtyp-Polymorphismus die gegebene Referenz auf einen Mammal oder einen seiner Subtypen zeigen kann.

An dieser Stelle können Sie wahrscheinlich die feinen Nuancen der Verwendung des Wortes class hier sehen. Es scheint, dass das, was wir tatsächlich aus der getClass()-Methode bekommen, irgendeine Art von Referenz ist, die wir verwenden, um auf die tatsächliche Klasse des ursprünglichen Objekts zu zeigen, wie wir bereits zuvor erläutert hatten.

Nun, das gleiche könnte über die asSubclass Methode gesagt werden. Zum Beispiel:

Mammal tiger = new Tiger(); 

Class<? extends Mammal> tigerAsMammal = tiger.getClass(); 
Class<? extends Feline> tigerAsFeline = tigerAsMammal.asSubclass(Feline.class); 

Wenn wir rufen asSubclass wir ein Hinweis auf die tatsächliche Art der Klasse werden immer durch unsere Referenz hingewiesen wird, aber der Compiler nicht mehr sicher, was sollte die tatsächliche Natur sein kann und daher Sie erhalten eine laxere Referenz wie Class<? extends Feline>. Dies ist der höchste Wert, den der Compiler von der ursprünglichen Natur des Objekts annehmen kann, und deshalb erhalten wir nur das, was wir bekommen.

Was ist mit neuen Tiger(). GerClass()?

Wir könnten erwarten, dass der einzige Weg, ein Class<Tiger> (ohne wirldcards) zu erhalten, durch den Zugriff auf das ursprüngliche Objekt sein sollte, nicht wahr? Wie:

Class<Tiger> tigerClass = new Tiger().getClass() 

Lustige Sache, obwohl wir immer erreichen das Tiger-Objekt durch einen Referenztyp, richtig ?. In Java haben wir nie direkten Zugriff auf die Objekte. Da wir ein Objekt immer über ihre Referenzen erreichen, kann der Compiler keine Annahmen über den tatsächlichen Typ der zurückgegebenen Referenz machen.

Das ist, warum auch dieser Code Class<? extend Tiger>

Class<? extends Tiger> tigerClass = new Tiger().getClass(); 

produzieren würde Das heißt, macht der Compiler keine Garantie darüber, was die new Betreiber hier zurückkehren. Für alles, was wichtig ist, könnte es ein Objekt zurückgeben, das mit Tiger kompatibel ist, aber nicht notwendig, dessen Klasse Tiger selbst ist.

Dies wird deutlicher, wenn Sie den Operator new für eine Factory-Methode ändern.

TigerFactory factory = new TigerFactory(); 
Class<? extends Tiger> tigerClass = tigerFactory.newTiger().getClass(); 

Und unsere Tiger Fabrik:

class TigerFactory { 
    public Tiger newTiger(){ 
    return new Tiger(){ } //notice this is an anonymous class 
    } 
} 

Ich hoffe, das zur Diskussion irgendwie beiträgt.

+0

Danke @Edwin Dalorzo, sehr klar. Der "Was ist der Grund für den Rückgabetyp der asSubclass-Methode" ist mir jetzt ganz klar. Nun, was wir bekommen, ist eine 'Klasse '. Wir können es sicher in eine 'Klasse ' umwandeln, ist es richtig? Nun, ich verstehe nicht, warum der Compiler dieses einfache Muster nicht erkennen kann und stattdessen diese ungeprüfte Warnung gibt. Gibt es Situationen, in denen meine allgemeine Annahme nicht gültig ist? – CptWasp