2009-02-26 18 views
32

In C# ich tatsächlich tun:generische Methode in Java ohne generisches Argument

//This is C# 
static T SomeMethod<T>() where T:new() 
{ 
    Console.WriteLine("Typeof T: "+typeof(T)); 
    return new T(); 
} 

//And call the method here 
SomeMethod<SomeClassName>(); 

Aber aus irgendeinem Grunde, warum ich es nicht in Java zu arbeiten bekommen.

Die Sache, die ich tun möchte, ist, eine statische Methode für eine Oberklasse zu erstellen, damit die Unterklassen in XML konvertiert werden können.

//This is Java, but doesn't work 
public static T fromXml<T>(String xml) { 
    try { 
    JAXBContext context = JAXBContext.newInstance(T.class); 
    Unmarshaller um = context.createUnmarshaller(); 
    return (T)um.unmarshal(new StringReader(xml)); 
    } catch (JAXBException je) { 
    throw new RuntimeException("Error interpreting XML response", je); 
    } 
} 

//Also the call doesn't work... 
fromXml<SomeSubObject>("<xml/>"); 

Antwort

48
public static <T> T fromXml(Class<T> clazz, String xml) { 

Genannt als:

Thing thing = fromXml(Thing.class, xml); 

oder expliziter:

Thing thing = MyClass.<Thing>fromXml(Thing.class, xml); 

noch mehr verwirrend können Sie Konstrukteure haben, dass sowohl eine generische Art zu konstruieren und eine generische haben Parameter selbst Ich kann mich nicht an die Syntax erinnern und habe sie nie in Wut gesehen (Sie sind wahrscheinlich mit einer statischen Erstellungsmethode sowieso besser dran).

Die Besetzung (T) ist unsicher, und Sie können T.class nicht schreiben. Also schließen Sie die T.class als Argument ein (wie JAXBContext.newInstance) und werfen Sie eine relevante Ausnahme, wenn der Typ falsch ist.

public static <T> T fromXml(Class<T> clazz, String xml) { 
    try { 
     JAXBContext context = JAXBContext.newInstance(clazz); 
     Unmarshaller um = context.createUnmarshaller(); 
     Object obj = um.unmarshal(new StringReader(xml)); 
     try { 
      return clazz.cast(obj); 
     } catch (ClassCastException exc) { 
      throw new RelevantException(
       "Expected class "+clazz+ 
        " but was "+obj.getClass() 
      ); 
     } 
    } catch (JAXBException exc) { 
     throw new RelevantException(
      "Error unmarshalling XML response", 
      exc 
     ); 
    } 
} 

Ich glaube, die nächste Version von JAXB (in 6u14?) Hat für diese Art der Sache einige bequemen Methoden in der JAXB Klasse.

+2

wird nicht einmal kompilieren ... Sie können nicht schreiben T.class – pgras

+2

Ich wollte nicht schreiben T.class in der Methode. Ich sagte als Argumnet. Und die aufrufende Funktion darf nicht (oder mag sie als argumnet bestanden haben). –

+0

Die newInstance (T.class) war eine Kopie & Paste die ich nicht sah. Immer noch scheint die Frage zu sein, wie man eine generische Methode schreibt. –

5

In Java sind Generika nur Kompilierzeitdaten, die zur Laufzeit verloren gehen. Also, wenn Sie eine solche Methode aufrufen würden, hätte die JVM keine Möglichkeit zu wissen, was T.class war. Der normale Weg, dies zu umgehen ist eine Klasseninstanz-Objekt als Parameter an die Methode übergeben werden, wie folgt aus:

public static <T> T fromXml(Class<T> clazz, String xml) { 
    try { 
    JAXBContext context = JAXBContext.newInstance(clazz); 
    Unmarshaller um = context.createUnmarshaller(); 
    return (T)um.unmarshal(new StringReader(xml)); 
    } catch (JAXBException je) { 
    throw new RuntimeException("Error interpreting XML response", je); 
    } 
} 

fromXml(SomeSubObject.class, "<xml/>"); 
+0

Noch nicht der Weg, eine generische Methode zu schreiben. –

+0

Fast. Die muss direkt nach dem "statischen" sein, nicht "fromXml", denke ich. – doekman

+0

Nicht ideal, nein. Wenn er jedoch zur Laufzeit Zugriff auf die Typinformationen benötigt (um JAXBContext.newInstance() aufzurufen), benötigt er ein Objekt dieser Klasse oder das Klassenobjekt. – Avi

1

fürchte ich, was Sie versuchen wird zu tun, einfach nicht in Java arbeiten. Das Erstellen neuer Instanzen von generischen Typen ist eine dieser "Muss" -Funktionen, die .NET bietet, während Java einfach fehlt. Dies führt zu "Workarounds" wie die zuvor vorgeschlagenen. Schauen Sie sich die ArrayList.toArray(T) als Referenz an, wie dies gemacht werden kann: Im Wesentlichen müssen Sie einen Verweis auf ein Objekt übergeben, das Sie erstellen möchten, damit Sie wissen, welche Klasse zur Laufzeit instanziiert wird. Dies ist der Code von ArrayList.toArray(T):

 

public <T> T[] toArray(T[] a) 
{ 
    if (a.length < size) 
    { 
     a = (T[]) java.lang.reflect.Array. 
     newInstance(a.getClass().getComponentType(), size); 
    } 
    System.arraycopy(elementData, 0, a, 0, size); 
    if (a.length > size) 
    { 
     a[size] = null; 
    } 
    return a; 
} 
 
2

Methoden wie Java Collections.emptySet() haben eine Signatur wie folgt aus:

public static final <T> Set<T> emptySet() 

und werden wie folgt aufgerufen:

Set<Foo> foos = Collections.<Foo>emptySet(); 

Mockito des anyObject() Verfahren ein anderes Beispiel ist . Ich persönlich finde keine der beiden Syntax so großartig. Den Typ als Methodenargument zu übergeben, funktioniert aber immer kludgy. Die Angabe des Parameters in der Art, wie emptySet() funktioniert, scheint sauberer zu sein, aber es ist nicht immer offensichtlich, dass eine Methode die Angabe eines Typs zulässt.

1

Die aktuelle Antwort ist sicherer, ist aber technisch veraltet, da Sie in java7 und darüber hinaus das Argument "Class clazz" nicht benötigen. Der Typ wird aus dem erwarteten Rückgabetyp abgeleitet.Sie erhalten nur eine Warnung über unsichere Besetzung.

Verwandte Themen