Understanding Java Reflection: Concepts, API Details, and Practical Code Examples
This article explains Java's reflection mechanism, covering how to obtain Class objects, inspect constructors, fields, and methods, invoke them dynamically, run main methods via reflection, use configuration files for flexible execution, and bypass generic type checks with reflective calls.
Reflection Is the Soul of Framework Design
Reflection allows a program to inspect and manipulate classes, methods, fields, and constructors at runtime, turning class components into Java objects that can be examined and invoked dynamically.
1. Overview of Reflection
Java's reflection mechanism enables you to discover all attributes and methods of any class and invoke them on any object during execution.
2. Class API Details (Java 1.7)
The Class object represents a loaded .class file. It is created by the JVM when a class is loaded.
2.1 Obtaining a Class Object
Three common ways to get a Class object:
Object instance: obj.getClass()
Static .class literal: MyClass.class
Dynamic loading: Class.forName("com.example.MyClass")
3. Using Reflection – Constructor Examples (Student class)
First, define a simple Student class with multiple constructors.
package fanshe;
public class Student {
public Student(String str) { System.out.println("(default) constructor s = " + str); }
public Student() { System.out.println("Public no‑arg constructor executed"); }
public Student(char name) { System.out.println("Name: " + name); }
public Student(String name, int age) { System.out.println("Name: " + name + ", Age: " + age); }
protected Student(boolean n) { System.out.println("Protected constructor n = " + n); }
private Student(int age) { System.out.println("Private constructor Age = " + age); }
}Test class to demonstrate constructor reflection:
package fanshe;
import java.lang.reflect.Constructor;
public class Constructors {
public static void main(String[] args) throws Exception {
Class clazz = Class.forName("fanshe.Student");
// All public constructors
for (Constructor c : clazz.getConstructors()) System.out.println(c);
// All constructors (including private, protected)
for (Constructor c : clazz.getDeclaredConstructors()) System.out.println(c);
// Obtain and invoke a public no‑arg constructor
Constructor con = clazz.getConstructor();
Object obj = con.newInstance();
// Obtain and invoke a private constructor
con = clazz.getDeclaredConstructor(char.class);
con.setAccessible(true);
obj = con.newInstance('M');
}
}4. Accessing Fields via Reflection
Define a Student class with various fields:
package fanshe.field;
public class Student {
public String name;
protected int age;
char sex;
private String phoneNum;
@Override public String toString() { return "Student [name="+name+", age="+age+", sex="+sex+", phoneNum="+phoneNum+"]"; }
}Reflection test to read and modify fields:
package fanshe.field;
import java.lang.reflect.Field;
public class Fields {
public static void main(String[] args) throws Exception {
Class clazz = Class.forName("fanshe.field.Student");
// Public fields
for (Field f : clazz.getFields()) System.out.println(f);
// All fields
for (Field f : clazz.getDeclaredFields()) System.out.println(f);
// Modify public field "name"
Field f = clazz.getField("name");
Object obj = clazz.getConstructor().newInstance();
f.set(obj, "Andy Lau");
System.out.println(((fanshe.field.Student)obj).name);
// Modify private field "phoneNum"
f = clazz.getDeclaredField("phoneNum");
f.setAccessible(true);
f.set(obj, "18888889999");
System.out.println(((fanshe.field.Student)obj).toString());
}
}5. Invoking Methods via Reflection
Define a Student class with several methods of different visibility:
package fanshe.method;
public class Student {
public void show1(String s) { System.out.println("Public show1: " + s); }
protected void show2() { System.out.println("Protected show2"); }
void show3() { System.out.println("Package‑private show3"); }
private String show4(int age) { System.out.println("Private show4 age=" + age); return "abcd"; }
}Reflection test to list and invoke methods:
package fanshe.method;
import java.lang.reflect.Method;
public class MethodClass {
public static void main(String[] args) throws Exception {
Class clazz = Class.forName("fanshe.method.Student");
// All public methods (including inherited)
for (Method m : clazz.getMethods()) System.out.println(m);
// All declared methods
for (Method m : clazz.getDeclaredMethods()) System.out.println(m);
// Invoke public method show1
Method m = clazz.getMethod("show1", String.class);
Object obj = clazz.getConstructor().newInstance();
m.invoke(obj, "Andy Lau");
// Invoke private method show4
m = clazz.getDeclaredMethod("show4", int.class);
m.setAccessible(true);
Object result = m.invoke(obj, 20);
System.out.println("Return value: " + result);
}
}6. Reflectively Invoking a Main Method
Student class with a static main method:
package fanshe.main;
public class Student {
public static void main(String[] args) { System.out.println("main method executed..."); }
}Reflection test to call that main :
package fanshe.main;
import java.lang.reflect.Method;
public class Main {
public static void main(String[] args) {
try {
Class clazz = Class.forName("fanshe.main.Student");
Method methodMain = clazz.getMethod("main", String[].class);
methodMain.invoke(null, (Object) new String[]{"a","b","c"});
} catch (Exception e) { e.printStackTrace(); }
}
}7. Configuration‑Driven Reflection
Using a properties file ( pro.txt ) to specify class and method names, allowing the program to run new code without recompilation.
# pro.txt
className=cn.fanshe.Student
methodName=showDemo that reads the configuration and invokes the specified method:
import java.io.FileReader;
import java.lang.reflect.Method;
import java.util.Properties;
public class Demo {
public static void main(String[] args) throws Exception {
Class clazz = Class.forName(getValue("className"));
Method m = clazz.getMethod(getValue("methodName"));
m.invoke(clazz.getConstructor().newInstance());
}
private static String getValue(String key) throws Exception {
Properties p = new Properties();
try (FileReader in = new FileReader("pro.txt")) { p.load(in); }
return p.getProperty(key);
}
}Changing the properties to point to Student2.show2 runs the new method without code changes.
8. Bypassing Generic Type Checks with Reflection
Generics are erased at runtime, so reflective calls can insert values of any type into a generic collection.
import java.lang.reflect.Method;
import java.util.ArrayList;
public class Demo {
public static void main(String[] args) throws Exception {
ArrayList
list = new ArrayList<>();
list.add("aaa");
list.add("bbb");
Method m = list.getClass().getMethod("add", Object.class);
m.invoke(list, 100); // inserts Integer into List
for (Object o : list) System.out.println(o);
}
}Output demonstrates that the integer value is stored alongside strings.
Conclusion
Reflection provides powerful capabilities for dynamic class inspection, object creation, field manipulation, method invocation, configuration‑driven execution, and even generic type circumvention, making it a core tool for many Java frameworks and advanced programming techniques.
Java Captain
Focused on Java technologies: SSM, the Spring ecosystem, microservices, MySQL, MyCat, clustering, distributed systems, middleware, Linux, networking, multithreading; occasionally covers DevOps tools like Jenkins, Nexus, Docker, ELK; shares practical tech insights and is dedicated to full‑stack Java development.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.