Java Reflection Mechanism

Time: Column:Java views:200

Reflection in Java is considered a key feature of dynamic languages. The Reflection mechanism allows a program to obtain internal information of any class during execution via the Reflection API, and directly manipulate the internal properties and methods of any object. Reflection is a powerful yet complex mechanism. The primary users of Reflection are tool developers, rather than application developers. If you're only interested in designing applications and not in building tools, there is no need to delve into it.

1. Features Provided by Reflection Mechanism (When to Use Reflection)

  1. Determine the class of any object at runtime.

  2. Create instances of any class at runtime.

  3. Determine the member variables and methods of any class at runtime.

  4. Invoke any object’s member variables and methods at runtime.

  5. Generate dynamic proxies.

2. The Class Class

During the program's runtime, the Java runtime system maintains a runtime type identifier for every object. This information tracks the class of each object. The virtual machine uses this runtime type information to select the appropriate method for execution. The class that stores this information is called Class. The getClass() method from the Object class returns an instance of the Class type. Below are the details:

public class ReflecTest {
    public static void main(String[] args) throws Exception {
        // Class is generic
        /*
            If a Person object represents a specific person, then a Class object will represent the properties of a specific class.
         */
        // There are three ways to get a Class, the first one is as follows:
        Class<Person> clazz = Person.class;
        
        // Create an object of the runtime class Person corresponding to clazz
        // Creating an object of the class: call the newInstance() method of the Class object: the class must have a no-argument constructor and the constructor must have sufficient access privileges.
        Person person = clazz.newInstance();
        
        // Alternatively, you can first create an object and use getClass() to get the Class object, from which you can deduce the structure of the target object
        Person personTest = new Person();
        // The second way to get a Class is as follows:
        Class<? extends Person> personTestClass = personTest.getClass();
        String clazzName = personTestClass.getName();
        //com.reflection.learn.Person
        System.out.printf(clazzName);

        // The third way to get a Class is as follows: through the static method of Class
        Class<?> className = Class.forName("com.reflection.learn.Person");

        // Use reflection to access specific attributes of the runtime class
        Field name = clazz.getField("name");
        // To set the attribute, you need to pass the **object** and the value
        // Set the public attribute
        name.set(person, "Li Si");

        // Access a private attribute
        Field age = clazz.getDeclaredField("age");
        age.setAccessible(true); // Allow access to private field
        age.set(person, 20);

        // Use reflection to invoke a method (no parameters)
        Method display = clazz.getMethod("display");
        // Execute this method, passing the object
        display.invoke(person);

        // If it's a static method, the invocation rule is as follows:
        // display.invoke(Person.class);

        // Call a method with parameters: public void getObject(String name)
        Method getObject = clazz.getMethod("getObject", String.class);
        // The method execution requires passing the object and parameter
        getObject.invoke(person, "Li Si");
    }
}

Explanation of java.lang.Class:
This class is the source of reflection: we create a class, and through the compiler (javac.exe), a corresponding .class file is generated. Then, by using java.exe (the JVM class loader), this .class file is loaded into memory, where it becomes a runtime class stored in the buffer. This runtime class itself is an instance of Class. A runtime class is loaded only once.

3. Invoking Specific Methods and Properties via Reflection (Important)

@Test
public void test2() throws Exception {
    // Get a specified constructor
    Class<Person> clazz = Person.class;
    // Get a specified constructor
    Constructor<Person> declaredConstructor = clazz.getDeclaredConstructor(String.class, int.class);
    Person person = declaredConstructor.newInstance("li", 20);
    // Output: Person{name='li', age=20}
    System.out.println(person.toString());

    // Get a specified method
    Method getObject = clazz.getDeclaredMethod("getObject", String.class);
    getObject.invoke(person, "zhangsan");
}

4. Using Reflection to Analyze Class Capabilities

In the java.lang.reflect package, there are three classes: Field (fields), Method (methods), and Constructor (constructors). Each of these classes has a method called getName(), which returns the name of the item. The Field class has a getType() method, which returns a Class object that describes the type of the field. Both the Method and Constructor classes have methods like getParameterTypes() to report their parameters. The Method class also has a getReturnType() method to report the return type.

These three classes also have a getModifiers() method, which returns an integer value. This integer describes the access modifiers (such as public, private, etc.) using bit flags. You can convert these integers into readable access modifiers using the Modifier class's static methods. The Class class provides methods like getDeclaredFields(), getDeclaredMethods(), and getDeclaredConstructors(), which return all the fields, methods, and constructors declared in the class, including private and protected members (but excluding inherited members).

A challenge with the get() method arises when a field is a String—it’s no problem treating it as an Object. However, for fields like salary, which is of type double, the primitive data types are not objects. In such cases, you can use the Field class's getDouble() method, or call get(), and the reflection mechanism will automatically box the value into the appropriate wrapper type (in this case, Double).