2010-01-19 12 views
33

Muestra de código:¿Es posible crear una instancia de clase anidada utilizando Java Reflection?

public class Foo 
{ 
    public class Bar 
    { 
     public void printMesg(String body) 
     { 
      System.out.println(body); 
     } 
    } 
    public static void main(String[] args) 
    { 
     // Creating new instance of 'Bar' using Class.forname - how? 
    }   
} 

¿Es posible crear una nueva instancia de la clase Bar dar su nombre? Traté de usar:

Class c = Class.forName("Foo$Bar") 

se encuentra la clase, pero cuando se utiliza c.newInstance() arroja InstantiationException.

+4

nitpick:.. Eso no es una clase anidada, es un elemento interno clase. Las clases anidadas son estáticas y se instancian fácilmente con el mecanismo que acaba de probar. – skaffman

+0

¿Cuáles fueron los detalles de 'InstantiationException'? –

+4

Las clases internas son un tipo de clase anidada (si recuerdo la terminología JLS correctamente). –

Respuesta

54

Tienes que saltar unos aros para hacerlo. En primer lugar, es necesario utilizar Class.getConstructor() para encontrar el objeto Constructor desea invocar:

Devuelve un objeto constructor que refleja la especificada pública constructor de la clase representada por este objeto clase. El parámetro parameterTypes es una matriz de objetos Clase que identifica los tipos de parámetros formales del constructor , en orden de aparición. Si este objeto Clase representa una clase interna declarada en un contexto no estático, los tipos de parámetros formales incluyen la instancia de inclusión explícita como el primer parámetro .

Y a continuación, utiliza Constructor.newInstance():

Si la clase que se declara de constructores es una clase interna en un contexto no estático, el primer argumento de la constructor tiene que ser el que encierra instancia

+0

Eso es lo que necesitaba. Gracias por la explicación completa! – kars7e

+0

Excelente explicación! – Jorge

2

Sí. Recuerde que necesita alimentar la instancia externa a una clase interna. Use javap para encontrar el constructor. Tendrá que pasar por java.lang.reflect.Constructor en lugar de confiar en el mal Class.newInstance.

Compiled from "Foo.java" 
public class Foo$Bar extends java.lang.Object{ 
    final Foo this$0; 
    public Foo$Bar(Foo); 
    public void printMesg(java.lang.String); 
} 

javap -c es interesante porque en el constructor (suponiendo -target 1.4 o temprano, ahora implícita) se obtiene una asignación de un campo de instancia antes de llamar al constructor de la superclase (solía ser ilegal).

public Foo$Bar(Foo); 
    Code: 
    0: aload_0 
    1: aload_1 
    2: putfield  #1; //Field this$0:LFoo; 
    5: aload_0 
    6: invokespecial #2; //Method java/lang/Object."<init>":()V 
    9: return 
+0

nunca antes había escuchado sobre javap. Gracias por mostrarme esa linda herramienta :). – kars7e

7

rápida y sucia código:

Foo.Bar.class.getConstructors()[0].newInstance(new Foo()); 

Explicación: Debe informar al Bar acerca de su Foo adjunto.

+1

Está sucio, pero es corto y funciona igual de bien :). Gracias. – kars7e

+0

Eso elude la mayor parte del problema ya que Bar puede no ser estático y/o puede que ni siquiera sea visible ... – Snicolas

+2

Erm, ¿mi respuesta asume que la clase no es estática? Y si la clase fuera invisible, OP obtendría una IllegalAccessException, no una InstantiationException ... – meriton

25

Las clases internas no se pueden construir sin construir primero la clase principal. No puede existir fuera de la clase principal. Tendrás que pasar una instancia de la clase principal cuando estés reflexionando. Las clases anidadas son static y se pueden usar independientemente de la clase principal, por lo tanto, también cuando se hace reflexión.

Aquí hay un SSCCE que muestra todas las cosas.

package mypackage; 

import java.lang.reflect.Modifier; 

public class Parent { 

    public static class Nested { 
     public Nested() { 
      System.out.println("Nested constructed"); 
     } 
    } 

    public class Inner { 
     public Inner() { 
      System.out.println("Inner constructed"); 
     } 
    } 

    public static void main(String... args) throws Exception { 
     // Construct nested class the normal way: 
     Nested nested = new Nested(); 

     // Construct inner class the normal way: 
     Inner inner = new Parent().new Inner(); 

     // Construct nested class by reflection: 
     Class.forName("mypackage.Parent$Nested").newInstance(); 

     // Construct inner class by reflection: 
     Object parent = Class.forName("mypackage.Parent").newInstance(); 
     for (Class<?> cls : parent.getClass().getDeclaredClasses()) { 
      if (!Modifier.isStatic(cls.getModifiers())) { 
       // This is an inner class. Pass the parent class in. 
       cls.getDeclaredConstructor(new Class[] { parent.getClass() }).newInstance(new Object[] { parent }); 
      } else { 
       // This is a nested class. You can also use it here as follows: 
       cls.getDeclaredConstructor(new Class[] {}).newInstance(new Object[] {}); 
      } 
     } 
    } 
} 

Esto debería producir

 
Nested constructed 
Inner constructed 
Nested constructed 
Inner constructed 
Nested constructed 
+2

¡Excelente y completo ejemplo! – Jorge

1

Otras respuestas han explicado cómo se puede a lo que quiere hacer.

Pero quiero sugerirte que el hecho de que necesites hacer esto es una indicación de que hay algo mal en el diseño de tu sistema. Sugeriría que necesites un método de fábrica (no estático) en la clase adjunta, o que necesites declarar la clase interna como estática.

La creación de una instancia de clase interna (no estática) tiene un "olor" a encapsulado roto.

-1

Aquí una respuesta de clase anidada (estática interno): En mi caso lo que necesito para adquirir el tipo por su nombre completo

Class.forName(somePackage.innerClass$outerClass).getConstructor().newInstance(); 

la '$' es crucial!

con un punto obtendrá ClassNotFoundException para la clase "package.innerClass.outerClass". La excepción se missleading :-(

+0

Eso no compila, ni es el orden de 'innerClass $ outerClass' correcto ... –

0

Esto no es del todo óptima, pero funciona para profundidades de las clases internas estáticas y clases internas

public <T> T instantiateClass(final Class<T> cls) throws CustomClassLoadException { 
    try { 
     List<Class<?>> toInstantiate = new ArrayList<Class<?>>(); 
     Class<?> parent = cls; 
     while (! Modifier.isStatic(parent.getModifiers()) && parent.isMemberClass()) { 
      toInstantiate.add(parent); 
      parent = parent.getDeclaringClass(); 
     } 
     toInstantiate.add(parent); 
     Collections.reverse(toInstantiate); 
     List<Object> instantiated = new ArrayList<Object>(); 
     for (Class<?> current : toInstantiate) { 
      if (instantiated.isEmpty()) { 
       instantiated.add(current.newInstance()); 
      } else { 
       Constructor<?> c = current.getConstructor(instantiated.get(instantiated.size() - 1).getClass()); 
       instantiated.add(c.newInstance(instantiated.get(instantiated.size() - 1))); 
      } 
     } 
     return (T) instantiated.get(instantiated.size() - 1); 
    } catch (InstantiationException e) { 
     throw new CustomClassLoadException("Failed to load class.", e); 
    } catch (IllegalAccessException e) { 
     throw new CustomClassLoadException("Failed to load class.", e); 
    } catch (SecurityException e) { 
     throw new CustomClassLoadException("Failed to load class.", e); 
    } catch (NoSuchMethodException e) { 
     throw new CustomClassLoadException("Failed to load class.", e); 
    } catch (IllegalArgumentException e) { 
     throw new CustomClassLoadException("Failed to load class.", e); 
    } catch (InvocationTargetException e) { 
     throw new CustomClassLoadException("Failed to load class.", e); 
    } 
} 
Cuestiones relacionadas