web-dev-qa-db-fra.com

Utilisation de la réflexion en Java pour créer une nouvelle instance avec le type de variable de référence défini sur le nouveau nom de classe d'instance?

Tous les exemples de réflexion que je recherche montrent la création d'une nouvelle instance d'une implémentation inconnue et la conversion de cette implémentation vers son interface. Le problème avec ceci est que maintenant vous ne pouvez plus appeler de nouvelles méthodes (seulement des substitutions) sur la classe d'implémentation, car votre variable de référence d'objet a le type d'interface. Voici ce que j'ai

Class c = null;
try {
    c = Class.forName("com.path.to.ImplementationType");
} catch (ClassNotFoundException e) {
    e.printStackTrace();
}
InterfaceType interfaceType = null;
try {
    interfaceType = (InterfaceType)c.newInstance();
} catch (InstantiationException e) {
    e.printStackTrace();
} catch (IllegalAccessException e) {
    e.printStackTrace();
}

Si j'ai seulement une référence à "com.path.to.ImplementationType" et si je ne sais pas ce que ce type pourrait être (il provient d'un fichier de configuration), alors comment puis-je utiliser le nom de la classe pour le transtyper ImplementationType? Est-ce seulement possible?

19
user4903

Cette ligne semble résumer le noeud de votre problème:

Le problème avec ceci est que maintenant vous ne pouvez plus appeler de nouvelles méthodes (seulement des substitutions) sur la classe d'implémentation, car votre variable de référence d'objet a le type d'interface.

Vous êtes plutôt coincé dans votre implémentation actuelle, car vous devez non seulement tenter un transtypage, mais vous avez également besoin de la définition de la ou des méthodes que vous souhaitez appeler dans cette sous-classe. Je vois deux options:

1. Comme indiqué ailleurs, vous ne pouvez pas utiliser la représentation sous forme de chaîne du nom de classe pour convertir votre instance reflétée en un type connu. Vous pouvez toutefois utiliser un test Stringequals() pour déterminer si votre classe est du type souhaité, puis effectuer un transtypage codé en dur:

try {
   String className = "com.path.to.ImplementationType";// really passed in from config
   Class c = Class.forName(className);
   InterfaceType interfaceType = (InterfaceType)c.newInstance();
   if (className.equals("com.path.to.ImplementationType") {
      ((ImplementationType)interfaceType).doSomethingOnlyICanDo();
   } 
} catch (Exception e) {
   e.printStackTrace();
}

Cela a l'air assez moche et ruine le processus que vous avez mis en place selon la configuration de Nice. Je ne vous suggère pas de faire cela, ce n'est qu'un exemple. 

2. Une autre option consiste à étendre votre réflexion de la Class/Object création à la Method réflexion. Si vous pouvez créer la variable Class à partir d'une chaîne transmise depuis un fichier de configuration, vous pouvez également transmettre un nom de méthode à partir de ce fichier de configuration et obtenir, par réflexion, une instance de la variable Method elle-même à partir de votre objet Class. Vous pouvez ensuite appeler invoke ( http://Java.Sun.com/javase/6/docs/api/Java/lang/reflect/Method.html#invoke(Java.lang.Object , Java.lang. Object ...)) sur la Method, en transmettant l'instance de votre classe que vous avez créée. Je pense que cela vous aidera à obtenir ce que vous cherchez.

Voici quelques exemples de code. Notez que j'ai pris la liberté de coder en dur les paramètres pour les méthodes. Vous pouvez également les spécifier dans une configuration, et vous devrez réfléchir à leurs noms de classe pour définir leurs objes et instances Class.

public class Foo {

    public void printAMessage() {
    System.out.println(toString()+":a message");
    }
    public void printAnotherMessage(String theString) {
        System.out.println(toString()+":another message:" + theString);
    }

    public static void main(String[] args) {
        Class c = null;
        try {
            c = Class.forName("Foo");
            Method method1 = c.getDeclaredMethod("printAMessage", new Class[]{});
            Method method2 = c.getDeclaredMethod("printAnotherMessage", new Class[]{String.class});
            Object o = c.newInstance();
            System.out.println("this is my instance:" + o.toString());
            method1.invoke(o);
            method2.invoke(o, "this is my message, from a config file, of course");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException nsme){
            nsme.printStackTrace();
        } catch (IllegalAccessException iae) {
            iae.printStackTrace();
        } catch (InstantiationException ie) {
            ie.printStackTrace();
        } catch (InvocationTargetException ite) {
            ite.printStackTrace();
        }
    }
}

et ma sortie:

this is my instance:Foo@e0cf70
Foo@e0cf70:a message
Foo@e0cf70:another message:this is my message, from a config file, of course
29
akf
//====Single Class Reference used to retrieve object for fields and initial values. Performance enhancing only====          
Class<?>    reference           =   vector.get(0).getClass();
Object      obj                 =   reference.newInstance();
Field[]     objFields           =   obj.getClass().getFields(); 
3
GoToJack

En complément de la réponse de akf, vous pouvez utiliser instanceof checks au lieu d'appels String equals ():

String cname="com.some.vendor.Impl";
try {
  Class c=this.getClass().getClassLoader().loadClass(cname);
  Object o= c.newInstance();
  if(o instanceof Spam) {
    Spam spam=(Spam) o;
    process(spam);
  }
  else if(o instanceof Ham) {
    Ham ham = (Ham) o;
    process(ham);
  }
  /* etcetera */
}
catch(SecurityException se) {
  System.err.printf("Someone trying to game the system?%nOr a rename is in order because this JVM doesn't feel comfortable with: “%s”", cname);
  se.printStackTrace();
}
catch(LinkageError le) {
  System.err.printf("Seems like a bad class to this JVM: “%s”.", cname);
  le.printStackTrace();
}
catch(RuntimeException re) { 
  // runtime exceptions I might have forgotten. Classloaders are wont to produce those.
  re.printStackTrace();
}
catch(Exception e) { 
  e.printStackTrace();
}

Notez le codage dur libéral de certaines valeurs. Quoi qu'il en soit, les points principaux sont:

  1. Utilisez instanceof plutôt qu'égaux (). Si quelque chose, il va mieux coopérer lors de la refactorisation.
  2. Assurez-vous d’attraper ces erreurs d’exécution et de sécurité également.
2
user268396

Vous voulez pouvoir passer dans une classe et obtenir une instance de type-safe de cette classe? Essayez ce qui suit:

public static void main(String [] args) throws Exception {
    String s = instanceOf(String.class);
}

public static <T> T instanceOf (Class<T> clazz) throws Exception {
    return clazz.newInstance();
}
1
Kevin

Je ne suis pas absolument sûr d'avoir bien compris votre question, mais il semble que vous souhaitiez quelque chose comme ceci:

    Class c = null;
    try {
        c = Class.forName("com.path.to.ImplementationType");
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    }
    T interfaceType = null;
    try {
        interfaceType = (T) c.newInstance();
    } catch (InstantiationException e) {
        e.printStackTrace();
    } catch (IllegalAccessException e) {
        e.printStackTrace();
    }

Où T peut être défini au niveau de la méthode ou au niveau de la classe, c.-à-d. <T extends InterfaceType>

1
nanda

Si vous connaissiez la Class de ImplementationType, vous pouvez en créer une instance. Donc, ce que vous essayez de faire n'est pas possible.

0
fastcodejava