¿Por qué debería usar la reflexión?

29

Soy nuevo en Java; A través de mis estudios, leí que la reflexión se usa para invocar clases y métodos, y para saber qué métodos se implementan o no.

¿Cuándo debo usar la reflexión, y cuál es la diferencia entre usar la reflexión y crear instancias de objetos y llamar a los métodos de la manera tradicional?

Hamzah khammash
fuente
55
Echa un vistazo a stackoverflow.com/questions/37628/…
Jalayn
10
Haga su parte de investigación antes de publicar. Hay mucho material en StackExchange (como señaló @Jalayn) y en la web en general sobre la reflexión. Le sugiero que lea, por ejemplo, el Tutorial de Java sobre Reflexión y regrese después de eso si tiene más preguntas concretas.
Péter Török
1
Tiene que haber un millón de tontos.
DeadMG
3
Más de unos pocos programadores profesionales responderían "tan raramente como sea posible, quizás incluso nunca".
Ross Patterson el

Respuestas:

38
  • La reflexión es mucho más lenta que simplemente llamar a los métodos por su nombre, porque tiene que inspeccionar los metadatos en el código de bytes en lugar de solo usar direcciones y constantes precompiladas.

  • La reflexión también es más poderoso: puede recuperar la definición de una protectedo finalmiembro, quitar la protección y manipularlo como si hubiera sido declarado mutable! Obviamente, esto subvierte muchas de las garantías que el lenguaje normalmente hace para sus programas y puede ser muy, muy peligroso.

Y esto prácticamente explica cuándo usarlo. Por lo general, no lo hagas. Si desea llamar a un método, simplemente llámelo. Si desea mutar un miembro, simplemente declare mutable en lugar de ir detrás de la compilación.

Un uso útil de la reflexión en el mundo real es cuando se escribe un marco que tiene que interoperar con clases definidas por el usuario, donde el autor del marco no sabe cuáles serán los miembros (o incluso las clases). La reflexión les permite tratar con cualquier clase sin saberlo de antemano. Por ejemplo, no creo que sea posible escribir una biblioteca compleja orientada a aspectos sin reflexión.

Como otro ejemplo, JUnit solía usar un poco de reflexión trivial: enumera todos los métodos en su clase, supone que todos los llamados testXXXson métodos de prueba y ejecuta solo esos. Pero esto ahora se puede hacer mejor con anotaciones, y de hecho JUnit 4 se ha movido en gran medida a las anotaciones.

Kilian Foth
fuente
66
"Más poderoso" necesita atención. No necesita reflexión para obtener la integridad de Turing, por lo que ningún cálculo necesita reflexión. Por supuesto, Turing complete no dice nada sobre otros tipos de potencia, como las capacidades de E / S y, por supuesto, la reflexión.
Steve314
1
La reflexión no es necesariamente "mucho más lenta". Puede usar la reflexión una vez para generar un bytecode de contenedor de llamada directa.
SK-logic
2
Lo sabrás cuando lo necesites. A menudo me preguntaba por qué (fuera de la generación del lenguaje) uno lo necesitaría. Luego, de repente, lo hice ... Tuve que caminar hacia arriba y hacia abajo por las cadenas padre / hijo para mirar / meter datos cuando recibí paneles de otros desarrolladores para que aparecieran en uno de los sistemas que mantengo.
Brian Knoblauch
@ SK-logic: en realidad para generar bytecode no necesitas reflexión (¡de hecho, la reflexión no contiene API para la manipulación de bytecode!).
Joachim Sauer
1
@JoachimSauer, por supuesto, pero necesitará una API de reflexión para cargar este bytecode generado.
SK-logic
15

Una vez fui como tú, no sabía mucho sobre la reflexión, todavía no lo sé, pero lo usé una vez.

Tuve una clase con dos clases internas, y cada clase tenía muchos métodos.

Necesitaba invocar todos los métodos en la clase interna, e invocarlos manualmente habría sido demasiado trabajo.

Utilizando la reflexión, podría invocar todos estos métodos en solo 2-3 líneas de código, en lugar del número de los métodos en sí.

Mahmoud Hossam
fuente
44
¿Por qué el voto negativo?
Mahmoud Hossam
1
Votación a favor del preámbulo
Dmitry Minkovsky
1
@MahmoudHossam Quizás no sea una práctica recomendada, pero su respuesta ilustra una táctica importante que se puede implementar.
ankush981
13

Agruparía los usos de la reflexión en tres grupos:

  1. Instanciar clases arbitrarias. Por ejemplo, en un marco de inyección de dependencias, probablemente declare que la interfaz ThingDoer está implementada por la clase NetworkThingDoer. El marco buscaría el constructor de NetworkThingDoer y lo instanciaría.
  2. Cálculo y desarmado a algún otro formato. Por ejemplo, mapear un objeto con getters y configuraciones que siguen la convención de bean a JSON y viceversa. El código en realidad no conoce los nombres de los campos o los métodos, solo examina la clase.
  3. Ajustar una clase en una capa de redireccionamiento (tal vez esa Lista no está realmente cargada, sino solo un puntero a algo que sabe cómo obtenerla de la base de datos) o falsificar una clase por completo (jMock creará una clase sintética que implementa una interfaz para fines de prueba).
Kevin Peterson
fuente
Esta es la mejor explicación de reflexión que he encontrado en StackExchange. La mayoría de las respuestas repiten lo que dice Java Trail (que es "puedes acceder a todas estas propiedades", pero no por qué lo harías), brindan ejemplos de hacer cosas con reflexión que son mucho más fáciles de hacer, o dan una respuesta vaga sobre cómo Spring lo usa Esta respuesta en realidad da tres ejemplos válidos que JVM NO PUEDE solucionar fácilmente sin reflexionar. ¡Gracias!
ndm13
3

Reflection permite que un programa funcione con código que puede no estar presente y que lo haga de manera confiable.

El "código normal" tiene fragmentos como los URLConnection c = nullque por su simple presencia hacen que el cargador de clases cargue la clase URLConnection como parte de la carga de esta clase, lanzando una excepción ClassNotFound y saliendo.

Reflection le permite cargar clases basadas en sus nombres en forma de cadena y probarlas para varias propiedades (útiles para múltiples versiones fuera de su control) antes de iniciar clases reales que dependen de ellas. Un ejemplo típico es el código específico de OS X utilizado para hacer que los programas Java parezcan nativos en OS X, que no están presentes en otras plataformas.


fuente
2

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 Classclase.

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.Proxyclase, 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.
Sebastian Redl
fuente
1

Reflection puede mantener automáticamente sincronizadas partes de su programa, donde anteriormente, habría tenido que actualizar manualmente su programa para usar las nuevas interfaces.

DeadMG
fuente
55
El precio que paga en este caso es que pierde la verificación de tipo por parte del compilador y la seguridad del refactor en el IDE. Es una compensación que no estoy dispuesto a hacer.
Barend