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:
- Object instance:
myObject.getClass() - Class literal:
String.class - 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>fromList<Integer>at runtime. getMethod("add", String.class)will fail on aList<String>because the compiled method signature expectsObject, notString8888.
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
setattranddel17171717.
# 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. |