2010-02-07 9 views
17

Tutti gli esempi a cui faccio riferimento per la riflessione mostrano la creazione di una nuova istanza di un'implementazione sconosciuta e la relativa implementazione alla sua interfaccia. Il problema con questo è che ora non è possibile chiamare nuovi metodi (solo sostituzioni) sulla classe di implementazione, poiché la variabile di riferimento oggetto ha il tipo di interfaccia. Ecco quello che ho:Utilizzo della riflessione in Java per creare una nuova istanza con il tipo di variabile di riferimento impostato sul nuovo nome della classe di istanza?

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

Se ho solo un riferimento a "com.path.to.ImplementationType", e io non so che cosa questo tipo potrebbe essere (è venuta da un file di configurazione) , quindi come posso usare il nome della classe per lanciarlo su ImplementationType? È possibile?

risposta

28

Questa linea sembra riassumere il nocciolo del problema:

Il problema con questo è che ora non è possibile chiamare tutti i nuovi metodi (solo override) sul implementazione della classe, poiché la variabile di riferimento dell'oggetto ha il tipo di interfaccia.

Sei abbastanza bloccato nella tua implementazione attuale, in quanto non solo devi provare un cast, hai anche bisogno della definizione dei metodi che vuoi chiamare in questa sottoclasse. Vedo due opzioni:

1. 1. Come specificato in precedenza, non è possibile utilizzare la rappresentazione String del nome della classe per trasmettere l'istanza riflessa a un tipo noto. È possibile, tuttavia, utilizzare un test Stringequals() per determinare se la classe è del tipo che si desidera, e quindi eseguire un cast hard-coded:

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

Questo sembra piuttosto brutto, e rovina la bella config -avviato il processo che hai Non ti suggerisco di farlo, è solo un esempio.

2. Un'altra opzione che avete è quella di estendere la riflessione a partire da soli Class/Object creazione per includere Method riflessione. Se è possibile creare Class da una stringa trasferita da un file di configurazione, è anche possibile passare un nome di metodo da tale file di configurazione e, tramite riflessione, ottenere un'istanza dello Method dall'oggetto Class. È quindi possibile chiamare invoke (http://java.sun.com/javase/6/docs/api/java/lang/reflect/Method.html#invoke(java.lang.Object, java.lang.Object ...)) su Method passando l'istanza della classe creata. Penso che questo ti aiuterà a ottenere ciò che cerchi.

Ecco un codice per servire da esempio. Nota che mi sono preso la libertà di codificare i parametri per i metodi. Potresti specificarli anche in una configurazione e riflettere sui nomi delle loro classi per definire i loro obietti e istanze 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(); 
     } 
    } 
} 

e la mia uscita:

this is my instance:[email protected] 
[email protected]:a message 
[email protected]:another message:this is my message, from a config file, of course 
+0

Dove posso scaricare il vaso per i metodi '' InterfaceType' e ImplementationType'? –

0

Se conoscessi il Class di ImplementationType, potresti creare un'istanza di esso. Quindi quello che stai cercando di fare non è possibile.

1

Si desidera essere in grado di passare una classe e ottenere un'istanza sicura al tipo di quella classe? Provare quanto segue:

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

io non sono assolutamente sicuro ho ricevuto correttamente la tua domanda, ma sembra che si desidera qualcosa di simile:

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

dove T può essere definito a livello di metodo oa livello di classe, ovvero <T extends InterfaceType>

2

Come addendum alla risposta di akf è possibile utilizzare instanceof checks invece di String equals() chiama:

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

Nota il codificatore progressivo di alcuni valori. Ad ogni modo i punti principali sono:

  1. Utilizzare instanceof piuttosto che equals(). Se mai, coopererà meglio durante il refactoring.
  2. Assicurati di prendere anche questi errori di runtime e quelli di sicurezza.
3
//====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(); 
+1

Ecco come ho risolto la necessità di recuperare informazioni per creare una nuova istanza da una classe sconosciuta per utilizzare i suoi campi per un'altra ricerca generica (per campo/valore eo filtri). Dimentica il commento "performance", ha qualcosa a che fare con alcune righe dopo queste. – GoToJack

Problemi correlati