Fundamentalmente, la reflexión significa usar el código de su programa como datos.
Por lo tanto, usar la reflexión puede ser una buena idea cuando el código de su programa es una fuente útil de datos. (Pero hay compensaciones, por lo que no siempre es una buena idea).
Por ejemplo, considere una clase simple:
public class Foo {
public int value;
public string anotherValue;
}
y quieres generar XML a partir de él. Podría escribir código para generar el XML:
public XmlNode generateXml(Foo foo) {
XmlElement root = new XmlElement("Foo");
XmlElement valueElement = new XmlElement("value");
valueElement.add(new XmlText(Integer.toString(foo.value)));
root.add(valueElement);
XmlElement anotherValueElement = new XmlElement("anotherValue");
anotherValueElement.add(new XmlText(foo.anotherValue));
root.add(anotherValueElement);
return root;
}
Pero esto es mucho código repetitivo, y cada vez que cambia la clase, debe actualizar el código. Realmente, podría describir lo que hace este código como
- crear un elemento XML con el nombre de la clase
- para cada propiedad de la clase
- crear un elemento XML con el nombre de la propiedad
- poner el valor de la propiedad en el elemento XML
- agregar el elemento XML a la raíz
Este es un algoritmo, y la entrada del algoritmo es la clase: necesitamos su nombre y los nombres, tipos y valores de sus propiedades. Aquí es donde entra en juego la reflexión: le da acceso a esta información. Java le permite inspeccionar tipos utilizando los métodos de la Class
clase.
Algunos casos de uso más:
- definir URL en un servidor web basado en los nombres de los métodos de una clase y parámetros de URL basados en los argumentos del método
- convertir la estructura de una clase en una definición de tipo GraphQL
- llame a todos los métodos de una clase cuyo nombre comience con "prueba" como un caso de prueba unitaria
Sin embargo, la reflexión completa significa no solo mirar el código existente (que en sí mismo se conoce como "introspección"), sino también modificar o generar código. Hay dos casos de uso prominentes en Java para esto: proxies y simulacros.
Digamos que tienes una interfaz:
public interface Froobnicator {
void froobnicateFruits(List<Fruit> fruits);
void froobnicateFuel(Fuel fuel);
// lots of other things to froobnicate
}
y tienes una implementación que hace algo interesante:
public class PowerFroobnicator implements Froobnicator {
// awesome implementations
}
Y, de hecho, también tiene una segunda implementación:
public class EnergySaverFroobnicator implements Froobnicator {
// efficient implementations
}
Ahora también desea una salida de registro; simplemente desea un mensaje de registro cada vez que se llama a un método. Podría agregar la salida de registro a cada método explícitamente, pero eso sería molesto, y tendría que hacerlo dos veces; una vez para cada implementación. (Entonces aún más cuando agrega más implementaciones).
En cambio, puede escribir un proxy:
public class LoggingFroobnicator implements Froobnicator {
private Logger logger;
private Froobnicator inner;
// constructor that sets those two
public void froobnicateFruits(List<Fruit> fruits) {
logger.logDebug("froobnicateFruits called");
inner.froobnicateFruits(fruits);
}
public void froobnicateFuel(Fuel fuel) {
logger.logDebug("froobnicateFuel( called");
inner.froobnicateFuel(fuel);
}
// lots of other things to froobnicate
}
De nuevo, sin embargo, hay un patrón repetitivo que puede ser descrito por un algoritmo:
- un proxy de registrador es una clase que implementa una interfaz
- tiene un constructor que toma otra implementación de la interfaz y un registrador
- para cada método en la interfaz
- la implementación registra un mensaje "$ methodname llamado"
- y luego llama al mismo método en la interfaz interna, pasando todos los argumentos
y la entrada de este algoritmo es la definición de la interfaz.
Reflection le permite definir una nueva clase usando este algoritmo. Java le permite hacer esto usando los métodos de la java.lang.reflect.Proxy
clase, y hay bibliotecas que le dan aún más poder.
¿Cuáles son las desventajas de la reflexión?
- Su código se vuelve más difícil de entender. Usted es un nivel de abstracción más alejado de los efectos concretos de su código.
- Su código se vuelve más difícil de depurar. Especialmente con las bibliotecas generadoras de código, el código que se ejecuta puede no ser el código que usted escribió, sino el código que generó, y el depurador puede no ser capaz de mostrarle ese código (o permitirle colocar puntos de interrupción).
- Tu código se vuelve más lento. La lectura dinámica de la información de tipo y el acceso a los campos mediante sus manejadores de tiempo de ejecución en lugar de la codificación de acceso es más lenta La generación dinámica de código puede mitigar este efecto, a costa de ser aún más difícil de depurar.
- Su código puede volverse más frágil. El compilador no verifica el acceso de reflexión dinámica, pero arroja errores en tiempo de ejecución.