2009-02-26 9 views
32

En C# que pueda hacer realidad esto:método genérico en Java sin discusión genérica

//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>(); 

Pero por alguna razón no puedo conseguir que funcione en Java.

Lo que quiero hacer es crear un método estático en una superclase para que las subclases se conviertan a XML.

//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/>"); 

Respuesta

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

llamado como:

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

o más explícitamente:

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

Para ser aún más confuso que puede tener constructores que tanto la construcción de un tipo genérico y tienen un genérico parámetro ellos mismos. No puedo recordar la sintaxis y nunca la he visto usar con enojo (de todos modos, es mejor que tengas un método de creación estático).

El molde (T) no es seguro, y no puede escribir T.class. Por lo tanto, incluya el T.class como argumento (como JAXBContext.newInstance) y genere una excepción relevante si el tipo es incorrecto.

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 
     ); 
    } 
} 

creo que la próxima versión de JAXB (en 6u14?) Tiene algunos métodos de conveniencia para este tipo de cosas en la clase JAXB.

+2

ni siquiera compilará ... No se puede escribir T.class – pgras

+2

No fue mi intención escribir T.class en el método. Dije como un argumnet. Y la función de llamada puede no (o puede haber pasado como una argumnet). –

+0

La nueva instancia (T.class) era una copia y pega que no vi. Todavía la pregunta parece ser sobre cómo escribir un método genérico. –

5

En Java, los genéricos son datos solo de tiempo de compilación, que se pierden en el tiempo de ejecución. Entonces, si llamaras a un método como ese, la JVM no tendría forma de saber qué era T.class. La manera normal de evitar esto es pasar un objeto de instancia de clase como un parámetro para el método, así:

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

Todavía no es la forma de escribir un método genérico. –

+0

Casi. El debe estar justo después de "static", no "fromXml", creo. – doekman

+0

No idealmente, no. Pero si va a necesitar acceder a la información de tipo en tiempo de ejecución (para llamar a JAXBContext.newInstance()), necesitará un objeto de esa clase, o el objeto de clase. – Avi

1

Temo lo que está tratando de hacer, simplemente no funciona en Java. No poder crear nuevas instancias de tipos genéricos es una de esas características "imprescindibles" que proporciona .NET mientras que simplemente falta Java. Esto lleva a "soluciones" como las sugeridas anteriormente. Eche un vistazo a ArrayList.toArray(T) como referencia de cómo se puede hacer esto: esencialmente, tendrá que pasar una referencia a un objeto que está tratando de crear para saber qué clase instanciar en el tiempo de ejecución. Este es el código de 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

Métodos como Java de Collections.emptySet() tienen una firma así:

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

y se llaman así:

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

método de Mockito anyObject() es otro ejemplo . Personalmente, no creo que ninguna de las dos sintaxis sea increíble. Pasar el tipo como un argumento de método funciona pero siempre se sintió kludgy. Proporcionar el parámetro en la forma en que emptySet() parece más limpio, pero no siempre es evidente que un método permita especificar un tipo.

1

La respuesta actual es más segura pero está técnicamente desactualizada porque en java7 y más allá no necesita el argumento "clazz de clase". El tipo se deduce del tipo de retorno esperado.Acabas de recibir una advertencia sobre el lanzamiento inseguro.

public class Main { 

    private static class Dog { 
     public String toString() { return "dog"; } 
    } 

    private static class Cat { 
     public String toString() { return "cat"; } 
    } 

    public static void main(String[] args) { 
     Cat cat = parse("cat"); 
     Dog dog = parse("dog"); 
     System.out.println("the cat object is a " + cat); 
     System.out.println("the dog object is a " + dog); 
    } 

    private static Object untypedParse(String stringToParse) { 
     if(stringToParse.equals("dog")) { 
      return new Dog(); 
     } else if(stringToParse.equals("cat")) { 
      return new Cat(); 
     } else { 
      throw new RuntimeException("not expected"); 
     } 
    } 

    public static <T> T parse(String stringToParse) { 
     return (T)untypedParse(stringToParse); 
    } 

} 


~/test/generics$ javac Main.java 
Note: Main.java uses unchecked or unsafe operations. 
Note: Recompile with -Xlint:unchecked for details. 
~/test/generics$ java Main 
the cat object is a cat 
the dog object is a dog 
Cuestiones relacionadas