What is Reflection?

Reflection enables a program to observe and modify its own structure and behavior. t bridges the gap between the base level (the application logic) and the meta level (objects representing the program itself).

Core Concepts

  • Introspection: The program can observe its own type information at run time.
  • Intercession (Dynamic Manipulation): The program can modify its own structure and behavior (e.g., adding methods, changing fields).
  • Reflective Code Generation: Creating executable code from data structures at run time.

8.1 Introspection

Introspection is the ability to examine the runtime type of an object. The entry point for this in Java is the Class object.

Obtaining Class Objects

There are three main ways to get a Class object in Java:

  1. Object instance: myObject.getClass()
  2. Class literal: String.class
  3. String name: Class.forName("java.lang.String") (Can throw runtime errors)

Inspecting Members

Once you have the Class object, you can retrieve its members.

  • getMethods() / getFields(): Returns public members (including inherited ones).
  • getDeclaredMethods() / getDeclaredFields(): Returns all members declared in the class (including private ones, but excluding inherited ones).

[cite_start]Safety & Access: To access private members, you must suppress Java’s access checking using setAccessible(true)[cite: 147]. This breaks information hiding and encapsulation.

Example: Universal toString

A generic toString method can be written using reflection to iterate over all fields of any object.

public static String getString(Object o) {
    if (o == null) return "null";
    Class c = o.getClass();
    // We use getDeclaredFields to see private state
    Field[] fields = c.getDeclaredFields(); 
    for (Field f : fields) {
        if (!Modifier.isStatic(f.getModifiers())) {
             f.setAccessible(true); // Bypass private check
             // ... read value ...
        }
    }
}

Safety checks (like IllegalAccessException) are moved from compile time to run time1111.

Dynamic Invocation (Unit Testing)

Reflection allows invoking methods when their names are not known at compile time.

  • Mechanism: Method.invoke(Object receiver, Object... args)2.
  • Use Case (JUnit): Test drivers can find all methods starting with “test” and invoke them dynamically. This decouples the test runner from the specific test classes3.

Reflective Visitor Pattern

Standard Visitor relies on accept methods in every element class. The Reflective Visitor uses introspection to dispatch calls, removing the need for accept4444.

  • Mechanism: The visitor constructs the method name dynamically based on the element’s class.
    // "magic" string construction
    String name = "visit" + e.getClass().getName();
    Method m = this.getClass().getMethod(name, e.getClass());
    m.invoke(this, e);
    
  • Pros: Simpler code in element classes, flexible lookup.
  • Cons: Not statically safe (runtime crash if method missing), slower performance.

Generics and Erasure

Due to Java’s erasure semantics, generic type parameters are not available via reflection on instances.

  • Example: You cannot distinguish List<String> from List<Integer> at runtime.
  • getMethod("add", String.class) will fail on a List<String> because the compiled method signature expects Object, not String8888.

8.2 Reflective Code Generation

This involves creating executable code from data structures at runtime9.

Why Reflection?

If code is represented as data, programs can create code from data. This allows adapting the program to user input or the environment (e.g., generating a specific SQL query based on a user filter)10.

C# Expression Trees

In C#, code (specifically lambda expressions) can be treated as data structures called Expression Trees11.

  • Structure: Trees composed of BinaryExpression (operators), ParameterExpression (variables), etc.12.
  • Compilation: A .Compile() method turns the tree data structure into executable code (a delegate) at runtime.
// Builds the tree for: x < c
Expression.Lambda<Func<int, bool>>(
    Expression.LessThan(lhs, ce), // The binary expression node
    new ParameterExpression[] { lhs }
).Compile(); // Transforms tree -> executable function

8.3 Dynamic Code Manipulation

This refers to modifying the structure of a program (adding/removing methods) while it is running14.

Python Example

Dynamic languages like Python allow extensive manipulation (“Intercession”).

  • Monkey Patching: You can replace methods on a class at runtime15.
  • Example: A generic wrapper can be applied to all methods of a class to make them case-insensitive16.
  • Mechanism: Methods can be added, exchanged, or deleted using setattr and del17171717.
# Injecting a new method into a class at runtime
def new_method(self): return "patched"
setattr(MyClass, "method_name", new_method)

Reflection and Type Checking

Degree of Reflection Type Checking Implications
Introspection Code is type-checked once during compilation (though Class.forName is a runtime check)18.
Reflective Code Generation Code is type-checked once when it is created (runtime compilation)19.
Dynamic Code Manipulation Typically requires dynamic type checking as types change during execution20.

Summary: Pros and Cons

Benefits (Applications) Drawbacks
Flexibility: Plug-ins, dynamic architectures21. No Static Safety: Type errors occur at runtime (exceptions)22.
Serialization: Persisting objects generically23. Performance: Reflection is slower than direct calls24.
Testing: Generic test drivers (JUnit)25. Complexity: Code is harder to understand, debug, and maintain26.
Design Patterns: Simplifies patterns like Visitor27. Breaks Encapsulation: Can access private fields28.