2011-02-01 23 views
21

Quiero crear una instancia de una clase por el valor de una cadena. Encontré varios tutoriales que muestran varios métodos para hacer esto. La clase DEBE heredar de una cierta interfaz, ImplementMe que tiene un método especial llamado runMe(). Así que esto es lo que probé:Cargando una clase desde una cadena

ImplmentMe a = 
    (ImplementMe) ImplementMe.class 
        .getClassLoader() 
        .loadClass("my.package.IImplementedYou") 
        .newInstance(); 
a.runMe(); 

Funciona, pero es muy feo. Al menos esperaba no necesitar un elenco. Por favor dime que hay una mejor manera.

+2

Necesitarás el reparto independientemente. El compilador no puede promover automáticamente 'Object' a' ImplementMe'. –

+0

Eso es tan extraño. Pensé que al usar 'ImplementMe.class.getClassLoader()', tomaría la sugerencia de que la clase debe heredar de 'ImplementMe'..oh bien. – User1

+0

'Class' tiene un método' asSubclass'. Entonces '... loadClass (...). AsSubclass (ImplementMe.class) .newInstance()' devolverá una instancia 'ImplementMe' (si tiene éxito). – user2478398

Respuesta

9

Probar Class.forName("my.package.IImplementedYou").

+4

Puedes notar que todavía necesitará el yeso. –

2

En esencia, eso es lo que sucederá independientemente de si está utilizando un kit de herramientas de terceros para ello o no. La emisión del objeto será inherentemente obligatoria a menos que se espere un Object. Sin embargo, puede realizar una rutina que lo hace por usted:

public <T> T instantiateObject(String name, Class<T> cls) throws Exception { 
    return (T) Class.forName(name).newInstance(); 
} 

que se pueden utilizar:

AClass cls = instantiateObject("com.class.AClass", AClass.class); 

Pero si se llega a este punto, el String name es en realidad redundante (dada AClass es una clase concreta) Es lo mismo que:

public <T> T instantiateObject(Class<T> cls) throws Exception { 
    return (T) Class.forName(cls.getCanonicalName()).newInstance(); 
} 

que se pueden utilizar:

AClass cls = instantiateObject(AClass.class); 
+0

'(T) Class.forName (cls.getCanonicalName()). NewInstance();' ¿por qué es tan complicado? ¿Qué tal 'cls.newInstance();'? Pero en mi interpretación, la clase de interfaz está cargada, pero no la clase de implementación –

+0

Verdadera, 'cls.newInstance()' sería más corta. También usando el segundo enfoque tiene que ser una clase concreta. –

1

se puede acortar un poco como

ImplementMe a = (ImplementMe) Class 
           .forName("my.package.IImplementedYou") 
           .newInstance(); 

pero no puede deshacerse de la fundición. Puede haber una forma de evitar el reparto, pero solo si puede evitar el subproblema de cargar la clase por su nombre.

30

No, no hay mejor manera (por diseño). Se supone que no debes hacer esto, Java está diseñado como un lenguaje seguro para tipos. Sin embargo, puedo entender que a veces hay que hacer cosas como esta, y para que los efectos se puede crear una función de biblioteca de esta manera:

public <T> T instantiate(final String className, final Class<T> type){ 
    try{ 
     return type.cast(Class.forName(className).newInstance()); 
    } catch(InstantiationException 
      | IllegalAccessException 
      | ClassNotFoundException e){ 
     throw new IllegalStateException(e); 
    } 
} 

Ahora su código de cliente, al menos, puede llamar a este método sin calidad:

MyInterface thingy = 
    instantiate("com.foo.bar.MyInterfaceImpl", MyInterface.class); 
+1

No necesita lanzar sin marcar. Simplemente use 'return type.cast (Class.forName (className) .newInstance());'. La ventaja es que tira inmediatamente (es más rápido). – maaartinus

1

La alternativa es utilizar forName, pero no hay nada mejor que lo que actualmente tiene:

ImplementMe a = 
    (ImplementMe) Class.forName("my.package.IImplementedYou").newInstance(); 
a.runMe(); 

de hecho, forName utilizará getClassLoader().loadClass() detrás de escena para cargar la clase, como se puede ver en source code de Class.java.

0

Necesitará un molde, porque el compilador no puede decir por el código que el objeto es del tipo ImplementMe. Por lo tanto, requiere que el programador emita un molde, que arrojará una ClassCastException si el objeto no es una instancia de ImplementMe.

0

Lo que tiene puede funcionar, pero no tiene que cargar la clase utilizando el mismo cargador de clases que cargó ImplementMe.Esto debería funcionar igual de bien:

Object newInstance = this.getClass().getClassLoader().loadClass("my.package.IImplementedYou").newInstance(); 

Lo importante es que el cargador de clases conoce tanto el archivo de clase con la aplicación de "my.package.IImplementedYou" y la clase con la aplicación de "ImplementMe".

Es posible comprobar explícitamente que IImplementedYou realmente implementa ImplementMe así:

if(newInstance instanceof my.package.IImplementedYou) { 
    ((ImplementMe)newInstance).runMe(); 
} 

También puede comprobar que IImlementedYou realmente está implementando la interfaz antes de crear la instancia:

Class c = this.getClass().getClassLoader().loadClass("my.package.IImplementedYou"); 
if(ImplementMe.class.isAssignableFrom(c)) { 
    Object newInstance = c.newInstance(); 
} 
5

Así es como lo haría hazlo:

ImplementMe noCastNeeded = 
    this.getClassLoader() 
     .loadClass("my.package.IImplementedYou") 
     .asSubclass(ImplementMe.class).newInstance(); 

Hay algunas excepciones t o atrapar pero está bien, creo. :)