Buscar..


Introducción

Reflection es comúnmente utilizado por programas que requieren la capacidad de examinar o modificar el comportamiento en tiempo de ejecución de las aplicaciones que se ejecutan en la JVM. Java Reflection API se usa para ese propósito donde permite inspeccionar clases, interfaces, campos y métodos en tiempo de ejecución, sin saber sus nombres en el momento de la compilación. Y también hace posible instanciar nuevos objetos, e invocar métodos utilizando la reflexión.

Observaciones

Actuación

Tenga en cuenta que la reflexión puede disminuir el rendimiento, solo úselo cuando su tarea no pueda completarse sin reflexión.

Desde el tutorial de Java La API de reflexión :

Debido a que la reflexión involucra tipos que se resuelven dinámicamente, ciertas optimizaciones de máquinas virtuales Java no se pueden realizar. En consecuencia, las operaciones de reflexión tienen un rendimiento más lento que sus contrapartes no reflexivas, y deben evitarse en las secciones de código que se llaman con frecuencia en aplicaciones sensibles al rendimiento.

Introducción

Lo esencial

La API de reflexión permite verificar la estructura de clase del código en tiempo de ejecución e invocar el código dinámicamente. Esto es muy poderoso, pero también es peligroso ya que el compilador no es capaz de determinar estáticamente si las invocaciones dinámicas son válidas.

Un ejemplo simple sería obtener los constructores públicos y los métodos de una clase determinada:

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;

// This is a object representing the String class (not an instance of String!)
Class<String> clazz = String.class;

Constructor<?>[] constructors = clazz.getConstructors(); // returns all public constructors of String
Method[] methods = clazz.getMethods(); // returns all public methods from String and parents

Con esta información es posible instanciar el objeto y llamar a diferentes métodos dinámicamente.

Reflexión y tipos genéricos.

La información de tipo genérico está disponible para:

  • Parámetros del método, utilizando getGenericParameterTypes() .
  • Método devuelve los tipos, usando getGenericReturnType() .
  • Campos públicos , utilizando getGenericType .

El siguiente ejemplo muestra cómo extraer la información de tipo genérico en los tres casos:

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Map;

public class GenericTest {

    public static void main(final String[] args) throws Exception {
        final Method method = GenericTest.class.getMethod("testMethod", Map.class);
        final Field field = GenericTest.class.getField("testField");

        System.out.println("Method parameter:");
        final Type parameterType = method.getGenericParameterTypes()[0];
        displayGenericType(parameterType, "\t");

        System.out.println("Method return type:");
        final Type returnType = method.getGenericReturnType();
        displayGenericType(returnType, "\t");

        System.out.println("Field type:");
        final Type fieldType = field.getGenericType();
        displayGenericType(fieldType, "\t");

    }

    private static void displayGenericType(final Type type, final String prefix) {
        System.out.println(prefix + type.getTypeName());
        if (type instanceof ParameterizedType) {
            for (final Type subtype : ((ParameterizedType) type).getActualTypeArguments()) {
                displayGenericType(subtype, prefix + "\t");
            }
        }

    }

    public Map<String, Map<Integer, List<String>>> testField;

    public List<Number> testMethod(final Map<String, Double> arg) {
        return null;
    }

}

Esto resulta en el siguiente resultado:

Method parameter:
    java.util.Map<java.lang.String, java.lang.Double>
        java.lang.String
        java.lang.Double
Method return type:
    java.util.List<java.lang.Number>
        java.lang.Number
Field type:
    java.util.Map<java.lang.String, java.util.Map<java.lang.Integer, java.util.List<java.lang.String>>>
        java.lang.String
        java.util.Map<java.lang.Integer, java.util.List<java.lang.String>>
            java.lang.Integer
            java.util.List<java.lang.String>
                java.lang.String

Invocando un método

Usando la reflexión, se puede invocar un método de un objeto durante el tiempo de ejecución.

El ejemplo muestra cómo invocar los métodos de un objeto String .

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

String s = "Hello World!";

// method without parameters
// invoke s.length()
Method method1 = String.class.getMethod("length");
int length = (int) method1.invoke(s); // variable length contains "12"

// method with parameters
// invoke s.substring(6)
Method method2 = String.class.getMethod("substring", int.class);
String substring = (String) method2.invoke(s, 6); // variable substring contains "World!"

Obtención y configuración de campos

Usando la API de Reflection, es posible cambiar u obtener el valor de un campo en tiempo de ejecución. Por ejemplo, podría usarlo en una API para recuperar diferentes campos según un factor, como el sistema operativo. También puede eliminar modificadores como final para permitir que los campos de modificación sean finales.

Para hacerlo, deberá usar el método Clase # getField () de una manera como la que se muestra a continuación:

// Get the field in class SomeClass "NAME".
Field nameField = SomeClass.class.getDeclaredField("NAME");

// Get the field in class Field "modifiers". Note that it does not 
// need to be static
Field modifiersField = Field.class.getDeclaredField("modifiers");

// Allow access from anyone even if it's declared private
modifiersField.setAccessible(true);

// Get the modifiers on the "NAME" field as an int.
int existingModifiersOnNameField = nameField.getModifiers();

// Bitwise AND NOT Modifier.FINAL (16) on the existing modifiers
// Readup here https://en.wikipedia.org/wiki/Bitwise_operations_in_C
// if you're unsure what bitwise operations are.
int newModifiersOnNameField = existingModifiersOnNameField & ~Modifier.FINAL;

// Set the value of the modifiers field under an object for non-static fields
modifiersField.setInt(nameField, newModifiersOnNameField);

// Set it to be accessible. This overrides normal Java 
// private/protected/package/etc access control checks.
nameField.setAccessible(true);

 // Set the value of "NAME" here. Note the null argument. 
 // Pass null when modifying static fields, as there is no instance object
nameField.set(null, "Hacked by reflection...");

// Here I can directly access it. If needed, use reflection to get it. (Below)
System.out.println(SomeClass.NAME);

Obtener campos es mucho más fácil. Podemos usar Field # get () y sus variantes para obtener su valor:

// Get the field in class SomeClass "NAME".
Field nameField = SomeClass.class.getDeclaredField("NAME");

// Set accessible for private fields
nameField.setAccessible(true);

// Pass null as there is no instance, remember?
String name = (String) nameField.get(null);

Tenga en cuenta esto:

Cuando use Class # getDeclaredField , utilícelo para obtener un campo en la clase en sí:

class HackMe extends Hacked {
    public String iAmDeclared;
}

class Hacked {
    public String someState;
}

Aquí, HackMe#iAmDeclared es campo declarado. Sin embargo, HackMe#someState no es un campo declarado, ya que se hereda de su superclase, hackeado.

Constructor de llamadas

Obtención del objeto constructor

Puedes obtener la clase Constructor del objeto Class esta manera:

Class myClass = ... // get a class object
Constructor[] constructors = myClass.getConstructors();

Donde la variable de constructors tendrá una instancia de Constructor para cada constructor público declarado en la clase.

Si conoce los tipos de parámetros precisos del constructor al que desea acceder, puede filtrar el constructor específico. El siguiente ejemplo devuelve el constructor público de la clase dada que toma un Integer como parámetro:

Class myClass = ... // get a class object
Constructor constructor = myClass.getConstructor(new Class[]{Integer.class});

Si ningún constructor coincide con los argumentos de constructor NoSuchMethodException se lanza una NoSuchMethodException .

Nueva Instancia usando Objeto Constructor

Class myClass = MyObj.class // get a class object
Constructor constructor = myClass.getConstructor(Integer.class);
MyObj myObj = (MyObj) constructor.newInstance(Integer.valueOf(123));

Obteniendo las constantes de una enumeración

Dando esta enumeración como ejemplo:

enum Compass {
    NORTH(0),
    EAST(90),
    SOUTH(180),
    WEST(270);
    private int degree;
    Compass(int deg){
        degree = deg;
    }
    public int getDegree(){
        return degree;
    }
}

En Java, una clase de enumeración es como cualquier otra clase, pero tiene algunas constantes definidas para los valores de enumeración. Además, tiene un campo que es una matriz que contiene todos los valores y dos métodos estáticos con values() nombre values() y valueOf(String) .
Podemos ver esto si usamos Reflection para imprimir todos los campos de esta clase

for(Field f : Compass.class.getDeclaredFields())
    System.out.println(f.getName());

La salida será:

NORTE
ESTE
SUR
OESTE
la licenciatura
ENUM $ VALORES

Así que podríamos examinar clases de enumeración con Reflexión como cualquier otra clase. Pero la API de Reflection ofrece tres métodos específicos de enumeración.

control de enumeración

Compass.class.isEnum();

Devuelve true para las clases que representan un tipo de enumeración.

recuperando valores

Object[] values = Compass.class.getEnumConstants();

Devuelve una matriz de todos los valores de enumeración como Compass.values ​​() pero sin la necesidad de una instancia.

enumeración constante

for(Field f : Compass.class.getDeclaredFields()){
    if(f.isEnumConstant())
        System.out.println(f.getName());
}

Enumera todos los campos de clase que son valores de enumeración.

Obtener clase dado su nombre (totalmente calificado)

Dada una String contiene el nombre de una clase, se puede acceder a su objeto de Class usando Class.forName :

Class clazz = null;
try {
    clazz = Class.forName("java.lang.Integer");
} catch (ClassNotFoundException ex) {
    throw new IllegalStateException(ex);
}
Java SE 1.2

Puede especificarse, si la clase debería inicializarse (segundo parámetro de forName ) y qué ClassLoader debería usarse (tercer parámetro):

ClassLoader classLoader = ...
boolean initialize = ...
Class clazz = null;
try {
    clazz = Class.forName("java.lang.Integer", initialize, classLoader);
} catch (ClassNotFoundException ex) {
    throw new IllegalStateException(ex);
}

Llamar a los constructores sobrecargados utilizando la reflexión.

Ejemplo: invocar diferentes constructores pasando parámetros relevantes

import java.lang.reflect.*;

class NewInstanceWithReflection{
    public NewInstanceWithReflection(){
        System.out.println("Default constructor");
    }
    public NewInstanceWithReflection( String a){
        System.out.println("Constructor :String => "+a);
    }
    public static void main(String args[]) throws Exception {
        
        NewInstanceWithReflection object = (NewInstanceWithReflection)Class.forName("NewInstanceWithReflection").newInstance();
        Constructor constructor = NewInstanceWithReflection.class.getDeclaredConstructor( new Class[] {String.class});
        NewInstanceWithReflection object1 = (NewInstanceWithReflection)constructor.newInstance(new Object[]{"StackOverFlow"});
        
    }
}

salida:

Default constructor
Constructor :String => StackOverFlow

Explicación:

  1. Crear instancia de clase usando Class.forName : llama al constructor predeterminado
  2. Invoque getDeclaredConstructor de la clase pasando tipos de parámetros como Class array
  3. Después de obtener el constructor, cree newInstance pasando un valor de parámetro como Object array

Uso incorrecto de la API de Reflection para cambiar variables privadas y finales

La reflexión es útil cuando se usa correctamente para el propósito correcto. Al utilizar la reflexión, puede acceder a las variables privadas y reinicializar las variables finales.

A continuación se muestra el fragmento de código, que no se recomienda.

import java.lang.reflect.*;

public class ReflectionDemo{
    public static void main(String args[]){
        try{
            Field[] fields = A.class.getDeclaredFields();
            A a = new A();
            for ( Field field:fields ) {
                if(field.getName().equalsIgnoreCase("name")){
                    field.setAccessible(true);
                    field.set(a, "StackOverFlow");
                    System.out.println("A.name="+field.get(a));
                }
                if(field.getName().equalsIgnoreCase("age")){
                    field.set(a, 20);
                    System.out.println("A.age="+field.get(a));
                }
                if(field.getName().equalsIgnoreCase("rep")){
                    field.setAccessible(true);
                    field.set(a,"New Reputation");
                    System.out.println("A.rep="+field.get(a));
                }
                if(field.getName().equalsIgnoreCase("count")){
                    field.set(a,25);
                    System.out.println("A.count="+field.get(a));
                }
            }                
        }catch(Exception err){
            err.printStackTrace();
        }
    }
}

class A {
    private String name;
    public int age;
    public final String rep;
    public static int count=0;
    
    public A(){
        name = "Unset";
        age = 0;
        rep = "Reputation";
        count++;
    }
}

Salida:

A.name=StackOverFlow
A.age=20
A.rep=New Reputation
A.count=25

Explicación:

En el escenario normal, no se puede acceder a private variables private fuera de la clase declarada (sin los métodos get y set). final variables final no pueden ser reasignadas después de la inicialización.

Reflection rompe ambas barreras se puede utilizar para cambiar las variables privadas y finales como se explicó anteriormente.

field.setAccessible(true) es la clave para lograr la funcionalidad deseada.

Call constructor de clase anidada

Si desea crear una instancia de una clase anidada interna, debe proporcionar un objeto de clase de la clase envolvente como un parámetro adicional con la Clase # getDeclaredConstructor .

public class Enclosing{
    public class Nested{
    public Nested(String a){
            System.out.println("Constructor :String => "+a);
        }
    }       
    public static void main(String args[]) throws Exception {
        Class<?> clazzEnclosing = Class.forName("Enclosing");            
        Class<?> clazzNested = Class.forName("Enclosing$Nested");
        Enclosing objEnclosing = (Enclosing)clazzEnclosing.newInstance();
        Constructor<?> constructor = clazzNested.getDeclaredConstructor(new Class[]{Enclosing.class, String.class});
        Nested objInner = (Nested)constructor.newInstance(new Object[]{objEnclosing, "StackOverFlow"});
    }
}

Si la clase anidada es estática, no necesitará esta instancia adjunta.

Proxies dinámicos

Los proxies dinámicos realmente no tienen mucho que ver con Reflection, pero son parte de la API. Es básicamente una forma de crear una implementación dinámica de una interfaz. Esto podría ser útil al crear servicios de maqueta.
Un proxy dinámico es una instancia de una interfaz que se crea con el llamado controlador de invocación que intercepta todas las llamadas de método y permite el manejo de su invocación manualmente.

public class DynamicProxyTest {

    public interface MyInterface1{
        public void someMethod1();
        public int someMethod2(String s);
    }

    public interface MyInterface2{
        public void anotherMethod();
    }
   
    public static void main(String args[]) throws Exception {
        // the dynamic proxy class 
        Class<?> proxyClass = Proxy.getProxyClass(
                ClassLoader.getSystemClassLoader(),
                new Class[] {MyInterface1.class, MyInterface2.class});
        // the dynamic proxy class constructor
        Constructor<?> proxyConstructor = 
            proxyClass.getConstructor(InvocationHandler.class);

        // the invocation handler
        InvocationHandler handler = new InvocationHandler(){
            // this method is invoked for every proxy method call
            // method is the invoked method, args holds the method parameters
            // it must return the method result
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 
                String methodName = method.getName();

                if(methodName.equals("someMethod1")){
                    System.out.println("someMethod1 was invoked!");
                    return null;
                }
                if(methodName.equals("someMethod2")){
                    System.out.println("someMethod2 was invoked!");
                    System.out.println("Parameter: " + args[0]);
                    return 42;
                }
                if(methodName.equals("anotherMethod")){
                    System.out.println("anotherMethod was invoked!");
                    return null;
                }
                System.out.println("Unkown method!");
                return null;
            }
        };

        // create the dynamic proxy instances
        MyInterface1 i1 = (MyInterface1) proxyConstructor.newInstance(handler);
        MyInterface2 i2 = (MyInterface2) proxyConstructor.newInstance(handler);

        // and invoke some methods
        i1.someMethod1();
        i1.someMethod2("stackoverflow");
        i2.anotherMethod();
    }
}

El resultado de este código es este:

someMethod1 was invoked!
someMethod2 was invoked!
Parameter: stackoverflow
anotherMethod was invoked!

Evil hacks de Java con Reflection

La API de Reflection podría usarse para cambiar los valores de los campos privados y finales incluso en la biblioteca predeterminada de JDK. Esto podría usarse para manipular el comportamiento de algunas clases bien conocidas como veremos.

Lo que no es posible

Comencemos primero con la única limitación significa que el único campo que no podemos cambiar con Reflexión. Ese es el Java SecurityManager . Se declara en java.lang.System as

private static volatile SecurityManager security = null;

Pero no se incluirá en la clase del sistema si ejecutamos este código

for(Field f : System.class.getDeclaredFields())
    System.out.println(f);

fieldFilterMap debe al fieldFilterMap en sun.reflect.Reflection que contiene el mapa en sí y el campo de seguridad en System.class y los protege contra cualquier acceso con Reflection. Por eso no pudimos desactivar el SecurityManager .

Cuerdas locas

Cada cadena Java está representada por la JVM como una instancia de la clase de String . Sin embargo, en algunas situaciones, la JVM ahorra espacio de almacenamiento utilizando la misma instancia para las cadenas que son. Esto sucede para los literales de cadena, y también para las cadenas que han sido "internadas" llamando a String.intern() . Entonces, si tiene "hello" en su código varias veces, siempre será la misma instancia de objeto.

Se supone que las cuerdas son inmutables, pero es posible usar la reflexión "malvada" para cambiarlas. El siguiente ejemplo muestra cómo podemos cambiar los caracteres en una cadena reemplazando su campo de value .

public class CrazyStrings {
    static {
        try {
            Field f = String.class.getDeclaredField("value");
            f.setAccessible(true);
            f.set("hello", "you stink!".toCharArray());
        } catch (Exception e) {
        }
    }
    public static void main(String args[])  {
        System.out.println("hello");
    }
}

Así que este código imprimirá "apestas!"

1 = 42

La misma idea podría ser usada con la clase entera.

public class CrazyMath {
    static {
        try {
            Field value = Integer.class.getDeclaredField("value");    
            value.setAccessible(true);          
            value.setInt(Integer.valueOf(1), 42);      
        } catch (Exception e) {
        }
    }
    public static void main(String args[])  {
        System.out.println(Integer.valueOf(1));
    }
}

Todo es verdad

Y de acuerdo con este post de stackoverflow podemos usar la reflexión para hacer algo realmente malo.

public class Evil {    
    static {
        try {
            Field field = Boolean.class.getField("FALSE");
            field.setAccessible(true);
            Field modifiersField = Field.class.getDeclaredField("modifiers");
            modifiersField.setAccessible(true);
            modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
            field.set(null, true);
        } catch (Exception e) {
        }
    }
    public static void main(String args[]){
        System.out.format("Everything is %s", false);
    }
}

Tenga en cuenta que lo que estamos haciendo aquí hará que la JVM se comporte de forma inexplicable. Esto es muy peligroso.



Modified text is an extract of the original Stack Overflow Documentation
Licenciado bajo CC BY-SA 3.0
No afiliado a Stack Overflow