¿Qué es la reflexión y por qué es útil?

Resuelto Lehane asked hace 16 años • 25 respuestas

¿Qué es la reflexión y por qué es útil?

Estoy particularmente interesado en Java, pero supongo que los principios son los mismos en cualquier idioma.

Lehane avatar Sep 01 '08 15:09 Lehane
Aceptado

El nombre reflexión se utiliza para describir el código que puede inspeccionar otro código en el mismo sistema (o a sí mismo).

Por ejemplo, digamos que tiene un objeto de un tipo desconocido en Java y le gustaría llamar a un método 'hacerAlgo' si existe alguno. El sistema de escritura estática de Java no está realmente diseñado para soportar esto a menos que el objeto se ajuste a una interfaz conocida, pero usando la reflexión, su código puede mirar el objeto y descubrir si tiene un método llamado "hacer algo" y luego llamarlo si querer.

Entonces, para darle un ejemplo de código de esto en Java (imagine que el objeto en cuestión es foo):

Method method = foo.getClass().getMethod("doSomething", null);
method.invoke(foo, null);

Un caso de uso muy común en Java es el uso con anotaciones. JUnit 4, por ejemplo, utilizará la reflexión para buscar en sus clases métodos etiquetados con la anotación @Test y luego los llamará cuando ejecute la prueba unitaria.

Hay algunos buenos ejemplos de reflexión para comenzar en http://docs.oracle.com/javase/tutorial/reflect/index.html

Y finalmente, sí, los conceptos son bastante similares en otros lenguajes de tipado estático que admiten la reflexión (como C#). En lenguajes de tipado dinámico, el caso de uso descrito anteriormente es menos necesario (ya que el compilador permitirá que se llame a cualquier método en cualquier objeto, fallando en tiempo de ejecución si no existe), pero el segundo caso de buscar métodos que están marcados o trabajar de cierta manera todavía es común.

Actualización de un comentario:

La capacidad de inspeccionar el código en el sistema y ver tipos de objetos no es reflexión, sino más bien introspección de tipos. La reflexión es entonces la capacidad de realizar modificaciones en tiempo de ejecución haciendo uso de la introspección. La distinción es necesaria aquí ya que algunos lenguajes apoyan la introspección, pero no la reflexión. Un ejemplo de ello es C++.

Matt Sheppard avatar Sep 01 '2008 08:09 Matt Sheppard

La reflexión es la capacidad de un lenguaje para inspeccionar y llamar dinámicamente clases, métodos, atributos, etc. en tiempo de ejecución.

Por ejemplo, todos los objetos en Java tienen el método getClass(), que le permite determinar la clase del objeto incluso si no lo sabe en el momento de la compilación (por ejemplo, si lo declaró como un Object); esto puede parecer trivial, pero tal reflexión no es posible. en lenguajes menos dinámicos como C++. Los usos más avanzados le permiten enumerar y llamar a métodos, constructores, etc.

La reflexión es importante ya que permite escribir programas que no tienen que "saber" todo en tiempo de compilación, lo que los hace más dinámicos, ya que se pueden vincular en tiempo de ejecución. El código se puede escribir en interfaces conocidas, pero se pueden crear instancias de las clases reales que se utilizarán mediante la reflexión de los archivos de configuración.

Muchos marcos modernos utilizan ampliamente la reflexión por esta misma razón. La mayoría de los demás lenguajes modernos también utilizan la reflexión, y en los lenguajes de scripting (como Python) están aún más estrechamente integrados, ya que se siente más natural dentro del modelo de programación general de esos lenguajes.

Liedman avatar Sep 01 '2008 08:09 Liedman

Uno de mis usos favoritos de la reflexión es el siguiente método de volcado de Java. Toma cualquier objeto como parámetro y utiliza la API de reflexión de Java para imprimir cada nombre y valor de campo.

import java.lang.reflect.Array;
import java.lang.reflect.Field;

public static String dump(Object o, int callCount) {
    callCount++;
    StringBuffer tabs = new StringBuffer();
    for (int k = 0; k < callCount; k++) {
        tabs.append("\t");
    }
    StringBuffer buffer = new StringBuffer();
    Class oClass = o.getClass();
    if (oClass.isArray()) {
        buffer.append("\n");
        buffer.append(tabs.toString());
        buffer.append("[");
        for (int i = 0; i < Array.getLength(o); i++) {
            if (i < 0)
                buffer.append(",");
            Object value = Array.get(o, i);
            if (value.getClass().isPrimitive() ||
                    value.getClass() == java.lang.Long.class ||
                    value.getClass() == java.lang.String.class ||
                    value.getClass() == java.lang.Integer.class ||
                    value.getClass() == java.lang.Boolean.class
                    ) {
                buffer.append(value);
            } else {
                buffer.append(dump(value, callCount));
            }
        }
        buffer.append(tabs.toString());
        buffer.append("]\n");
    } else {
        buffer.append("\n");
        buffer.append(tabs.toString());
        buffer.append("{\n");
        while (oClass != null) {
            Field[] fields = oClass.getDeclaredFields();
            for (int i = 0; i < fields.length; i++) {
                buffer.append(tabs.toString());
                fields[i].setAccessible(true);
                buffer.append(fields[i].getName());
                buffer.append("=");
                try {
                    Object value = fields[i].get(o);
                    if (value != null) {
                        if (value.getClass().isPrimitive() ||
                                value.getClass() == java.lang.Long.class ||
                                value.getClass() == java.lang.String.class ||
                                value.getClass() == java.lang.Integer.class ||
                                value.getClass() == java.lang.Boolean.class
                                ) {
                            buffer.append(value);
                        } else {
                            buffer.append(dump(value, callCount));
                        }
                    }
                } catch (IllegalAccessException e) {
                    buffer.append(e.getMessage());
                }
                buffer.append("\n");
            }
            oClass = oClass.getSuperclass();
        }
        buffer.append(tabs.toString());
        buffer.append("}\n");
    }
    return buffer.toString();
}
Ben Williams avatar Sep 02 '2008 16:09 Ben Williams

Usos de la reflexión

La reflexión se utiliza comúnmente en programas que requieren la capacidad de examinar o modificar el comportamiento en tiempo de ejecución de las aplicaciones que se ejecutan en la máquina virtual Java. Esta es una característica relativamente avanzada y sólo deben utilizarla desarrolladores que tengan un conocimiento sólido de los fundamentos del lenguaje. Con esa advertencia en mente, la reflexión es una técnica poderosa y puede permitir que las aplicaciones realicen operaciones que de otro modo serían imposibles.

Funciones de extensibilidad

Una aplicación puede hacer uso de clases externas definidas por el usuario creando instancias de objetos de extensibilidad utilizando sus nombres completos. Navegadores de clases y entornos de desarrollo visual Un navegador de clases debe poder enumerar los miembros de las clases. Los entornos de desarrollo visual pueden beneficiarse del uso de información de tipos disponible en la reflexión para ayudar al desarrollador a escribir el código correcto. Depuradores y herramientas de prueba Los depuradores deben poder examinar miembros privados en clases. Los arneses de prueba pueden hacer uso de la reflexión para llamar sistemáticamente a un conjunto reconocible de API definidas en una clase, para garantizar un alto nivel de cobertura de código en un conjunto de pruebas.

Desventajas de la reflexión

La reflexión es poderosa, pero no debe usarse indiscriminadamente. Si es posible realizar una operación sin utilizar la reflexión, entonces es preferible evitar su utilización. Se deben tener en cuenta las siguientes preocupaciones al acceder al código mediante reflexión.

  • Gastos generales de rendimiento

Dado que la reflexión implica tipos que se resuelven dinámicamente, no se pueden realizar determinadas optimizaciones de la máquina virtual Java. En consecuencia, las operaciones reflectantes tienen un rendimiento más lento que sus contrapartes no reflectantes y deben evitarse en secciones de código que se llaman con frecuencia en aplicaciones sensibles al rendimiento.

  • Restricciones de seguridad

Reflection requiere un permiso de tiempo de ejecución que puede no estar presente cuando se ejecuta bajo un administrador de seguridad. Esta es una consideración importante para el código que debe ejecutarse en un contexto de seguridad restringido, como en un subprograma.

  • Exposición de partes internas

Dado que la reflexión permite que el código realice operaciones que serían ilegales en código no reflectante, como acceder a campos y métodos privados, el uso de la reflexión puede provocar efectos secundarios inesperados, que pueden hacer que el código sea disfuncional y destruir la portabilidad. El código reflectante rompe las abstracciones y, por lo tanto, puede cambiar el comportamiento con las actualizaciones de la plataforma.

fuente: La API de reflexión

Desmond Smith avatar Oct 17 '2014 11:10 Desmond Smith