2009-11-14 14 views
19

Vorrei creare un'istanza di una classe specificata utilizzando il suo nome. Il mio codice è mostrato sotto.Generics e Class.forName

Viene visualizzato un avviso del compilatore. Sto facendo questo nel modo giusto? È persino possibile usare il nome di una classe e ottenere un'istanza di quel tipo, poiché non penso che ci sia modo per il compilatore di sapere quale dovrebbe essere il tipo?

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

Grazie

risposta

28

penso che il primo metodo dovrebbe essere simile a questo:

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

Non si può fare un up-fuso typecast utilizzando un parametro di tipo ... senza quei fastidiosi tipo - avvertenze sulla sicurezza.

A proposito, se si ignorano tali avvisi, il metodo di creazione può creare un'istanza di una classe che non è compatibile con il tipo effettivo utilizzato dal chiamante. È probabile che ciò porti a un inaspettato ClassCastException in seguito; per esempio. quando l'istanza è assegnata.


EDIT: @Pascal sottolinea che abbiamo bisogno di aggiungere un typecast per rendere questa compilazione; vale a dire

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

Purtroppo, abbiamo anche bisogno di aggiungere un'annotazione @SuppressWarnings.

+0

Sì, questo è il più vicino si può ottenere con Java. Per quanto riguarda la creazione dell'oggetto reale ci sono ancora alcuni trucchi da fare, ma non ho intenzione di entrare nello specifico dato che questi trucchi sono implementati nella mia classe di utilità che sto pianificando di rilasciare non appena ho scoperto il giusto canale per diffonderlo tra tutti i programmatori Java là fuori. – Esko

+0

@Esko, considera il codice Google (per le versioni rilasciate) e/o Github (per il codice effettivo) –

+0

È necessario aggiungere un cast per renderlo compilato 'classe finale clazz = (Classe ) Class.forName (className). asSubclass (ifaceClass); ' –

2

Penso che questo sia dovuto al fatto che Class.forName(..) non è parametrizzato per T. Quando si attiva il completamento automatico dell'eclissi, si assume l'oggetto restituito clazz.newInstance(). Quindi, mantieni il cast e aggiungi @SuppressWarnings. Se non si utilizza il metodo correttamente (ad esempio String str = Utils.create("java.util.ArrayList");), si verificherà un ClassCastException, ma ciò è normale.

+0

create non genereranno un ClassCastException. Il codice che la chiama potrebbe, ma non necessariamente immediatamente. – meriton

+0

Beh, sì, ma questo dipende da come aggiusta i suoi metodi, perché ora non compilano - o ripianifica l'eccezione, o restituisce null (che causerà NPE) – Bozho

2

Il secondo metodo va bene.


Ma per il primo, qualsiasi nome di classe può essere passato come parametro.

Il punto del metodo sarebbe quello di instanciare una classe che il codice chiamante non conosce in fase di compilazione, lo sa solo al momento dell'esecuzione.

In queste condizioni, il codice chiamante non è possibile impostare il tipo di parametro, in modo che il metodo non può essere parametrizzato, così il cast non ha un punto ...

Perciò, direi l'intero metodo non ha alcun punto di .


Un certo punto potrebbe essere dato al metodo se il codice chiamante saprebbe un supertipo della classe. Questo potrebbe essere passato come parametro e il cast sarebbe possibile e significativo.

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

Controlla il metodo Class.cast (Object). – meriton

+0

(Per ottenere codice più breve ed eliminare tale avviso non selezionato) – meriton

+0

@Meriton True, se l'errore scelto per segnalare un cast impossibile è generare un ClassCastException. Ad esempio, questo codice restituisce null, un'opzione diversa ... Una scelta deve essere fatta ... ;-) – KLE

0

Anche se è possibile ottenere il codice qui sopra per lavorare, il metodo newInstance() del costruttore assume un costruttore argomento zero.

Se la Classe non ha uno i.e il costruttore dell'argomento zero della classe che stai tentando di creare è stato dichiarato esplicitamente privato, o pacchetto a seconda di dove viene chiamato il metodo, otterrai una IllegalAccessException. Qualcosa da aggiungere al tuo presupposto, se lo fai funzionare.

1

Non è possibile limitare un parametro di tipo per contenere il tipo nome className. Quindi, un chiamante può fornire qualsiasi tipo alla funzione create (String), che ovviamente non è sicura.

Poiché non è possibile applicare staticamente che l'istanza restituita implementa qualsiasi interfaccia (senza che il chiamante ti dica passando l'oggetto Class corrispondente), fare a meno dei generici e fare in modo che il chiamante esegua il cast in qualsiasi tipo si aspetti.

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

Un chiamante può quindi scrivere:

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

al contrario di

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

ho trovato una cosa interessante: Type possono essere convertiti in Class direttamente se non è un tipo generico . Ma non riesco ancora a 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