2009-06-16 15 views
49

Wenn ich eine Java-Klasse bin die Schaffung generisch sein, wie zum Beispiel:Java generische Klasse - Bestimmen Typ

public class Foo<T> 

Wie kann man intern bestimmen, zu dieser Klasse, was ‚T‘ endete als?

public ???? Bar() 
{ 
    //if its type 1 
    // do this 
    //if its type 2 
    // do this 
    //if its type 3 
    // do this 
    //if its type 4 
    // do this 
} 

Ich habe rund um die Java API stieß und mit der Reflexion Sachen gespielt, instanceof, getClass, .class-, etc, aber ich kann nicht scheinen, Kopf oder Zahl von ihnen zu machen. Ich habe das Gefühl, dass ich nah dran bin und nur eine Reihe von Anrufen kombinieren muss, aber ich komme immer wieder zu kurz.

Um genauer zu sein, versuche ich festzustellen, ob die Klasse mit einem von 3 möglichen Typen instanziiert wurde.

+15

Der Wunsch, dies zu tun, ist fast ein schlechtes Design Wahl sein garantiert. Es ist gut möglich, dass die if-Anweisung Kenntnisse über jeden "Typ" erfordert, damit sie implementiert werden können. Daher sollten Sie wahrscheinlich alle diese Klassen vom selben übergeordneten Typ haben, und "do this" sollte eine virtuelle Methode sein. Es kann beinhalten, Klassen zu umhüllen, aber insgesamt verbessert sich Ihr Design, wenn Sie diesen Weg gehen. –

+2

Die Möglichkeiten in den if-Anweisungen sind Kantenfälle unter allen möglichen Aufruftypen, die insgesamt völlig getrennte Implementierungen erfordern. Ich bin damit einverstanden, was Sie hier sagen, aber die spezifische Anwendung ist ein Stein und ein harter Ort. –

+1

Die Notwendigkeit, die Klasse für einen generischen Typparameter abzurufen, ist nicht immer eine schlechte Entwurfsentscheidung. Eine legitime Anforderung (meins!) Könnte ein Methodenaufruf sein, der einen Parameter der Klasse <> erfordert. – dahvyd

Antwort

58

Ich habe eine ähnliche Lösung verwendet, die er hier für ein paar Projekte erklärt und fand es ziemlich nützlich.

http://blog.xebia.com/2009/02/07/acessing-generic-types-at-runtime-in-java/

Der jist davon wird mit dem folgenden:

public Class returnedClass() { 
    ParameterizedType parameterizedType = (ParameterizedType)getClass() 
               .getGenericSuperclass(); 
    return (Class) parameterizedType.getActualTypeArguments()[0]; 
} 
+0

+1! Ich wollte darüber schreiben –

+0

+1 Und das ist so ziemlich die Lösung, mit der ich nach weiteren Recherchen ging, also habe ich es zu meiner akzeptierten Antwort geändert. –

+0

+1 ausgezeichnet! Ich habe es stundenlang gesucht ... –

12

Wegen type erasure gibt es keine Möglichkeit, dies direkt zu tun. Was Sie jedoch tun könnten, ist, geben Sie eine Class<T> in den Konstruktor und halten Sie es in Ihrer Klasse. Dann können Sie es mit den drei möglichen Class Typen überprüfen, die Sie zulassen.

Wenn es jedoch nur drei mögliche Typen gibt, sollten Sie stattdessen ein Refactoring in eine enum in Betracht ziehen.

+0

Dies scheint die beste Lösung, wenn auch weniger schön als eine echte blaue generische Klasse. Danke für die Eingabe. –

+0

Ich fordere Sie erneut auf, Polymorphismus und nicht eine auf Typ basierende if-Anweisung in Betracht zu ziehen. –

+0

Leider beziehen sich die betrachteten Punkte in keiner Weise auf eine polymorphe Lösung. Die möglichen Elemente, die in die if-Anweisungslogik fallen würden, sind Kantenfälle von allen anderen "normalen" Verarbeitungen, die gänzlich anders behandelt werden müssen, und zwar in keiner relationalen Weise. –

0

Der ganze Sinn einer allgemeinen Klasse ist, dass Sie die Art müssen nicht wissen, dass ....

+2

Zutreffend, aber wenn ich eine Klasse entwerfe, die generisch als beliebiger Typ instanziiert werden kann, möchte ich eine bestimmte Teilmenge aller möglichen Typen als Kantenfälle mit spezifischer Logik behandeln. –

+0

Erstellen Sie dann verschiedene Subtypen. Oder delegieren Sie an Strategien (oder ähnliches). –

3

Das Problem ist, verwendet wird, ist, dass die meisten der generischen Sachen während der Kompilierung verschwinden.

Eine übliche Lösung ist das Speichern des Typs während der Erstellung des Objekts.

Eine kurze Einführung in die Art Erasure Verhalten von Java lesen diese page

+1

Dies ist sehr hilfreich - ich habe Lösungen gesehen, die dies beinhalten und am Ende diesen Weg gehen können. –

+1

Wahrscheinlich bedeutet, dass Ihr Entwurf schlecht ist. –

0

Es sieht aus wie das, was Sie wollen, ist in der Tat nicht eine generische Klasse, sondern eine Schnittstelle mit einer Reihe von verschiedenen Implementierungen. Aber vielleicht würde es klarer werden, wenn Sie Ihr tatsächliches, konkretes Ziel angeben würden.

44

Im Gegensatz zu .NET Java werden Generika durch eine Technik namens "Type Erasure" implementiert.

Das bedeutet, dass der Compiler beim Generieren der Klassendateien die Typinformationen verwendet, diese Informationen jedoch nicht in den Bytecode überträgt. Wenn Sie sich die kompilierten Klassen mit javap oder ähnlichen Werkzeugen ansehen, werden Sie feststellen, dass ein List<String> ein einfacher List (von Object) in der Klassendatei ist, genauso wie es in Pre-Java-5-Code war.

Code, der auf die generische Liste zugreift, wird vom Compiler "neu geschrieben", um die Umwandlungen einzuschließen, die Sie selbst in früheren Versionen schreiben müssten. In der Tat sind die beiden folgenden Codefragmente aus einer Bytecode Perspektive identisch einmal die Compiler mit ihnen durchgeführt wird:

Java 5:

List<String> stringList = new ArrayList<String>(); 
stringList.add("Hello World"); 
String hw = stringList.get(0); 

Java 1.4 und vor:

List stringList = new ArrayList(); 
stringList.add("Hello World"); 
String hw = (String)stringList.get(0); 

Beim Lesen Werte aus einer generischen Klasse in Java 5 wird die notwendige Umwandlung in den deklarierten Typparameter automatisch eingefügt. Beim Einfügen prüft der Compiler den Wert, den Sie eingeben möchten, und bricht mit einem Fehler ab, wenn es sich nicht um einen String handelt.

Die ganze Sache wurde getan, um alte Bibliotheken und neuen generierten Code interoperabel zu halten, ohne die vorhandenen Bibliotheken neu kompilieren zu müssen. Dies ist ein großer Vorteil gegenüber der .NET-Methode, bei der generische Klassen und nicht-generische Klassen nebeneinander existieren, aber nicht beliebig untereinander ausgetauscht werden können.

Beide Ansätze haben ihre Vor- und Nachteile, aber so ist es in Java.

Um zu Ihrer ursprünglichen Frage zurückzukehren: Sie werden zur Laufzeit nicht in der Lage sein, an die Typinformationen zu kommen, weil sie einfach nicht mehr da sind, sobald der Compiler seine Arbeit getan hat. Dies ist sicherlich in gewisser Weise einschränkend und es gibt einige verschrobene Möglichkeiten, die normalerweise darauf basieren, irgendwo eine Klasseninstanz zu speichern, aber dies ist kein Standardmerkmal.

+2

+1 für umfangreiche Informationen und Kontrast mit C# - danke –

+1

Vielen Dank. Es tut mir leid, dass es keine Taste +10 gibt. –

0

ich mit Visage zustimmen. Generics dient zur Überprüfung der Kompilierungszeit und nicht zur dynamischen Typisierung der Laufzeit. Klingt wie, was Sie brauchen, ist wirklich nur das Fabrikmuster. Aber wenn dein "Tun" keine Instanziierung ist, dann wird ein einfaches Enum wahrscheinlich genauso gut funktionieren. Wie Michael sagte, wenn Sie ein etwas konkreteres Beispiel haben, erhalten Sie bessere Antworten.

+0

Ich muss hier widersprechen, und es gab mehrere Erwähnungen einer nicht-polymorphen Lösung, die in dem spezifischen Problemraum notwendig ist, und ich war ziemlich glücklich mit den Antworten, die ich erhalten habe. –

1

Wenn Sie einige spezifische Typen kennen, die aussagekräftig sind, sollten Sie mit der Implementierung Unterklassen Ihres generischen Typs erstellen.

So

public class Foo<T> 

public ???? Bar() 
{ 
    //else condition goes here 
} 

Und dann

public class DateFoo extends Foo<Date> 

public ???? Bar() 
{ 
    //Whatever you would have put in if(T == Date) would go here. 
} 
Verwandte Themen