web-dev-qa-db-fra.com

Méthode générique en Java sans argument générique

En C #, je peux réellement faire ceci:

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

Mais pour une raison quelconque, je ne peux pas le faire fonctionner en Java.

La chose que je veux faire est de créer une méthode statique sur une superclasse, afin que les sous-classes puissent être converties en 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/>");
35
doekman
public static <T> T fromXml(Class<T> clazz, String xml) {

Appelé comme:

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

ou plus explicitement:

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

Pour être encore plus déroutant, vous pouvez avoir des constructeurs qui à la fois construisent un type générique et ont eux-mêmes un paramètre générique. Je ne me souviens pas de la syntaxe et je ne l'ai jamais vue utilisée avec colère (vous êtes probablement mieux avec une méthode de création statique de toute façon).

Le casting (T) n'est pas sûr et vous ne pouvez pas écrire T.class. Incluez donc la classe T. comme argument (comme JAXBContext.newInstance does) et lève une exception pertinente si le type est incorrect.

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

Je crois que la prochaine version de JAXB (en 6u14?) A des méthodes pratiques pour ce genre de chose dans la classe JAXB.

52
Tom Hawtin - tackline

En Java, les génériques sont des données de compilation uniquement, qui sont perdues au moment de l'exécution. Donc, si vous appeliez une telle méthode, la JVM n'aurait aucun moyen de savoir ce que T.class était. La façon normale de contourner cela est de passer un objet d'instance de classe en tant que paramètre à la méthode, comme ceci:

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

Des méthodes comme la Collections.emptySet() de Java ont une signature comme celle-ci:

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

Et sont appelés comme ceci:

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

La méthode anyObject() de Mockito est un autre exemple. Personnellement, je ne trouve aucune de ces syntaxes très impressionnante. Passer le type en tant qu'argument de méthode fonctionne, mais cela m'a toujours semblé compliqué. Fournir le paramètre de la façon dont emptySet() semble plus propre mais il n'est pas toujours évident qu'une méthode permet de spécifier un type.

3
spaaarky21

La réponse actuelle est plus sûre mais techniquement dépassée car en Java7 et au-delà, vous n'avez pas besoin de l'argument "Class clazz". Le type est déduit du type de retour attendu. Vous recevez simplement un avertissement concernant une diffusion non sécurisée.

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
1
joseph

Je crains que ce que vous essayez de faire ne fonctionne tout simplement pas en Java. Ne pas être en mesure de créer de nouvelles instances de types génériques est l'une de ces fonctionnalités "indispensables" fournies par .NET alors que Java est simplement manquant. Cela conduit à des "solutions de contournement" comme celles suggérées précédemment. Vérifier out la ArrayList.toArray(T) comme référence comment cela peut être fait: vous devrez essentiellement passer une référence à un objet que vous essayez de créer afin de savoir quelle classe instancier au moment de l'exécution. Voici le code 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;
}
1
Bogdan