2009-11-14 9 views
19

Je voudrais créer une instance d'une classe spécifiée en utilisant son nom. Mon code est montré ci-dessous.Génériques et Class.forName

Je reçois un avertissement de compilateur. Est-ce que je fais cela de la bonne façon? Est-il même possible d'utiliser le nom d'une classe et de récupérer une instance de ce type, car je ne pense pas que le compilateur puisse connaître le type de ce type?

public static <T> T create(final String className) { 
    try { 
     final Class<?> clazz = Class.forName(className); 

     //WARNING: Type safety: Unchecked cast from capture#2-of ? to T 
     return (T) create(clazz); 
    } 
    catch (ClassNotFoundException e) { 
     e.printStackTrace(); 
    } 
} 

public static <T> T create(final Class<T> classToCreate) { 
    final Constructor<T> constructor; 
    try { 
     constructor = classToCreate.getDeclaredConstructor(); 
     final T result = constructor.newInstance(); 
     return result; 
    } 
    catch (Exception e) { 
     e.printStackTrace(); 
    } 
} 

Merci

Répondre

28

Je pense que la première méthode devrait ressembler à ceci:

public static <T> T create(final String className, Class<T> ifaceClass) 
throws ClassNotFoundException { 
    final Class<T> clazz = Class.forName(className).asSubclass(ifaceClass); 
    return create(clazz); 
} 

Vous ne pouvez pas faire une conversion de type en-cast en utilisant un paramètre de type ... sans ces types satanés -Avertissements de sécurité. À propos, si vous ignorez ces avertissements, la méthode create peut créer une instance d'une classe qui n'est pas compatible avec le type utilisé par l'appelant. Cela risque de conduire à une exception ClassCastException inattendue plus tard; par exemple. lorsque l'instance est assignée


EDIT: @Pascal signale que nous devons ajouter un modèle pour effectuer cette compilation; à savoir

Class<T> clazz = (Class<T>) Class.forName(className).asSubclass(ifaceClass); 

Malheureusement, nous aussi besoin d'ajouter une annotation @SuppressWarnings.

+0

Eh oui, cela est aussi proche que vous pouvez obtenir avec Java. Pour ce qui est de la création de l'Object, il reste encore quelques trucs à faire, mais je ne vais pas entrer dans les détails car ces trucs sont implémentés dans ma classe d'utilitaires que je prévois de lancer dès que je découvre canal pour le diffuser parmi tous les programmeurs Java là-bas. – Esko

+0

@Esko, considérez le code Google (pour les versions publiées) et/ou avec Github (pour le code réel) –

+0

Vous devez ajouter un cast pour le compiler 'final Classe clazz = (Classe ) Class.forName (className). asSubclass (ifaceClass); ' –

2

Je pense que c'est parce que Class.forName(..) n'est pas paramétré pour T. Lorsque vous déclenchez la saisie semi-automatique de l'éclipse, cela suppose l'objet de retour clazz.newInstance(). Conservez donc la distribution et ajoutez @SuppressWarnings. Si vous n'utilisez pas la méthode correctement (c'est-à-dire String str = Utils.create("java.util.ArrayList");), un ClassCastException se produira, mais c'est normal.

+0

create ne lancera pas une exception ClassCastException. Le code l'appelant peut, mais pas nécessairement immédiatement. – meriton

+0

Eh bien, oui, mais cela dépend de la façon dont il corrige ses méthodes, car maintenant, ils ne compilent pas - soit il relance l'exception, soit retourne null (ce qui provoquera NPE) – Bozho

2

La deuxième méthode est correcte. Mais pour le premier, n'importe quel nom de classe pourrait être passé en paramètre.

Le point de la méthode serait d'instancier une classe que le code appelant ne connait pas au moment de la compilation, il ne le sait qu'à l'exécution.

Dans ces conditions, le code d'appel ne peut pas régler le paramètre de type, de sorte que la méthode ne peut pas être paramétrés, de sorte que le casting n'a pas de point ...

Par conséquent, je dirais toute la méthode n'a pas de point .


Certains points pourraient être remis à la méthode si le code d'appel connaîtrait un supertype de la classe. Cela pourrait être passé en paramètre, et la distribution serait possible et significative.

public static <T> T create(final Class<T> superType, final String className) { 
    try { 
    final Class<?> clazz = Class.forName(className); 
    final Object object = clazz.newInstance(); 
    if (superType.isInstance(object)) { 
     return (T)object; // safe cast 
    } 
    return null; // or other error 
    } // catch and other error code 
} 
+0

Extraire la méthode Class.cast (Object). – meriton

+0

(Pour obtenir un code plus court et éliminer cet avertissement non contrôlé) – meriton

+0

@Meriton Vrai, si l'erreur choisie pour signaler une distribution impossible est de lancer une exception ClassCastException. Par exemple, ce code renvoie null, une option différente ... Un choix doit être fait ... ;-) – KLE

0

Même si vous pouvez obtenir le code ci-dessus pour travailler, la méthode newInstance() du constructeur suppose un constructeur sans argument.

Si la classe n'en a pas i.e le constructeur d'argument zéro de la classe que vous essayez de créer a été explicitement déclaré privé, ou package en fonction de l'origine de la méthode, vous obtiendrez une exception IllegalAccessException. Quelque chose à ajouter à votre précondition si vous le faites fonctionner.

1

Vous ne pouvez pas restreindre un paramètre de type pour qu'il contienne le type nommé className. Par conséquent, un appelant peut fournir n'importe quel type à votre fonction create (String), qui n'est bien sûr pas de type safe. Puisque vous ne pouvez pas appliquer de façon statique que l'instance retournée implémente une interface (sans que l'appelant ne vous le dise en passant l'objet Class correspondant), je me passerais de génériques et ferais passer l'appelant au type attendu.

public static Object create(final String className) { 
    try { 
     final Class<?> clazz = Class.forName(className); 
     return create(clazz); 
    } 
    catch (ClassNotFoundException e) { 
     e.printStackTrace(); 
    } 
} 

Un appelant peut alors écrire:

Foo foo = (Foo) create("my.cool.FooBar"); 

par opposition à

Foo foo = create("my.cool.FooBar", Foo.class); 
0

J'ai trouvé une chose intéressante: Type peut être converti en Class directement si elle n'est pas un type générique . Mais je ne peux toujours pas Class.forName("java.util.List<SomeType>").

import java.lang.reflect.*; 
import java.util.*; 

public class Main { 
    public void func(List<List<String>> listlist, Map<String, List<String>> list, String s) {} 

    public static void main(String[] args) throws ClassNotFoundException { 
     Method m = Main.class.getDeclaredMethods()[1]; 

     Type t0 = m.getGenericParameterTypes()[0]; 
     boolean b0 = t0 instanceof Class; 

     boolean b01 = ((ParameterizedType)t0).getRawType() instanceof Class; 

     Type t1 = m.getGenericParameterTypes()[2]; 
     boolean b1 = t1 instanceof Class; 
    } 
} 

enter image description here