Reglas de Reflexión != Reglas de No Reflexión

Lo que el compilador de Java conoce es diferente de lo que conoce el run time.
Esa diferencia se debe a varias cosas, entre ellas al “desconocimiento” que la máquina virtual tiene de “cosas” que el compilador sí conoce.
En ocasiones suceden cosas “extrañas”.
Es el caso de algunas diferencias importantes entre el código “normal” y el código que hace uso de reflexión.

A continuación algunos ejemplos de algunas de esas diferencias.

Ejemplo 1
La firma (signature) de los constructores de las clases inner son “distintas” para el compilador y para el run time. La máquina virtual “no sabe nada de clases inner”. La unidad de distribución de código en Java es la clase.

El código:

package innerclasses;

import java.lang.reflect.Constructor;
import static java.lang.System.out;

//La clase pública Main
public class Main {

  public static void main(String[] args) throws Exception {
   
    //Creamos un objeto de clase Outer
    Outer outerObject = new Outer();
   
    //Obtenemos los objetos Class que describen a las clases
    //Inner y Nested definidas dentro de Outer
    Class innerClass = outerObject.getInnerClass();
    Class nestedClass = outerObject.getNestedClass();
   
    //Creamos objetos de clase Nested e Inner con código normal
    //usando los constructores que el compilador "ve"
    Outer.Nested nestedObject = new Outer.Nested(4);
    Outer.Inner innerObject = new Outer().new Inner(3);
   
    //Creamos e incializamos objetos de clase Constructor
    Constructor innerConstructor1 = null;
    Constructor innerConstructor2 = null;
    Constructor nestedConstructor = null;
    try {
      //Aquí comienza el código con reflexión
      //Obtenemos el objeto Constructor real de la clase Inner
      innerConstructor1 = innerClass.getDeclaredConstructor(Outer.class, int.class);
      //Creamos un objeto de clase Inner
      innerConstructor1.newInstance(outerObject, 5);
     
      //Las dos siguientes líneas disparan una excepción en ejecución
      //Ese constructor no existe, aunque es el que "ve" el compilador
      innerConstructor2 = innerClass.getDeclaredConstructor(int.class);
      innerConstructor2.newInstance(5);
      //Obtenemos el objeto Constructor de la clase Nested
      //Ese constructor es el mismo que "ve" el compilador
      nestedConstructor = nestedClass.getDeclaredConstructor(int.class);
      //Creamos un objeto de clase Nested
      nestedConstructor.newInstance(5);
    } catch (Exception e) {
      e.printStackTrace();
    }

  }
}

//La clase Outer
class Outer {

  Outer() {
  }
 
  //Devuelve el objeto Class que describe a la clase Nested
  Class getNestedClass() {
    return Nested.class;
  }

  //Devuelve el objeto Class que describe la clase Inner
  Class getInnerClass() {
    return Inner.class;
  }

  //La clase interior Inner definida dentro de Outer
  class Inner {

    private int foo;

    Inner(int foo) {
      this.foo = foo;
      out.println(foo);
    }
  }

  //La clase anidada Nested definida dentro de Outer
  static class Nested {

    private int bar;

    Nested(int bar) {
      this.bar = bar;
      out.println(bar);
    }
  }
}

El código hace lo siguiente:

  1. Define la clase pública Main, una clase Outer, una clase Inner y una clase Nested.
  2. La clase Main incluye un pequeño programa que crea un objeto de clase Outer, un objeto de clase Inner y un objeto de clase Nested.
  3. Esos objetos se crean con código sin reflexión y con código con reflexión.
  4. Para el código sin reflexión creamos los objetos de clase Inner y de clase Nested con el constructor que el compilador ve. El compilador “sabe” que la clase Inner es una clase inner definida dentro de Outer y que Nested es una clase anidada definida también dentro de Outer. Distingo aquí las clases anidadas de las clases inner o interiores, porque son distintas.
  5. Para el código con reflexión obtengo primero el objeto Constructor para ambas clases, la clase Inner y la clase Outer. La sentencia
    innerConstructor2 = innerClass.getDeclaredConstructor(int.class);
    de manera “inocente”, considera que el constructor de la clase Inner tiene la firma Inner(int) y consecuentemente obtiene el objeto Constructor con la sentencia anterior.
  6. La sentencia
    innerConstructor1 = innerClass.getDeclaredConstructor(Outer.class, int.class);
    sabe que la máquina virtual desconoce que Inner es una clase interior de Outer, y entonces pide el constructor real que Inner tiene con la firma Inner(Outer, int). Una vez que un objeto de clase Inner(el objeto innerObject) se crea, está “atado” a un objeto de clase Outer (el objeto outerObject).
  7. Para la clase anidada Nested, tanto el compilador como la máquina virtual “ven” el mismo constructor con la firma Nested(int). No hay atadura entre los objetos outerObject y nestedObject en el programa.
  8. Al correr el programa se disparará la excepción
    java.lang.NoSuchMethodException: innerclasses.Outer$Inner.<init>(int)
    producida por la sentencia
    innerConstructor2 = innerClass.getDeclaredConstructor(int.class);
    Es lógico; ese constructor no existe aunque así lo escribimos en la clase Inner.
  9. Para ejecutar el programa, es necesario comentar o eliminar las sentencias incorrectas:
    innerConstructor2 = innerClass.getDeclaredConstructor(int.class);
          innerConstructor2.newInstance(5);

Moraleja
La firma de los constructores de clases internas no es la que escribimos para esas clases. Añade a la lista de sus argumentos como primer parámetro, una referencia a su clase Outer si deseas construir objetos mediante reflexión.

Ejemplo 2
Las reglas de acceso a métodos públicos de clases no públicas son diferentes con reflexión y sin reflexión
Pero ya es muy largo este post. Lo dejo para el siguiente.

Comentarios

Opciones de visualización de comentarios

Seleccione la forma que prefiera para mostrar los comentarios y haga clic en «Guardar las opciones» para activar los cambios.
Imagen de OscarRyz

Que tal bferro. Me gusta el

Que tal bferro.

Me gusta el tema, pero me costó un poco de trabajo seguir el hilo de lo que escribiste. Quizá sería más fácil de seguir si los comentarios estuvieran en el código, así no tendría que saltar hacia atrás y hacia adelante.

Por ejemplo, un comentario muy útil sería saber donde empieza el código normal y donde el código usando reflexión, puede parecer obvio cuando ya lo sabes, pero no lo es.

Saludos.

Imagen de bferro

Código comentado (Reglas de Reflexión != Reglas de No Reflexión)

Gracias OscarRyz por la sugerencia.
Ya quedó el código comentado