Reflection in Java is a powerful feature that allows a program to examine or introspect its own structure at runtime.
It enables the program to inspect and manipulate classes, interfaces, fields, methods, and constructors dynamically, without knowing their names at compile time.
Using reflection, we can do the following at runtime :
- Obtain class information : get information about a class, such as its name, superclass, implemented interfaces, and modifiers;
- Create instances : create objects of classes dynamically, even if the class name is not known until runtime;
- Access fields : access and modify the fields (variables) of a class, regardless of their access level (public, private, protected, or default);
- Invoke methods: invoke methods on objects, even if the method names are not known at compile time.
Reflection is primarily used in frameworks, libraries, and tools that need to work with unknown classes or provide extensibility points.
However, it’s important to note that while reflection can be a powerful tool, it should be used judiciously, as it may lead to reduced performance and can make the code less readable and maintainable.
What You Need
- About 10 minutes
- A favorite text editor or IDE
- Java 8 or later
3. Dynamic creation of instances of a class
The Reflection API allows to dynamically create instances of a class.
3.1 Creating objects using the Class class
The newInstance() method of the Class class is used to create an instance of the class and to invoke its default constructor.
public class ClassTest10 {
public static void main(String[] args) throws InstantiationException, IllegalAccessException {
Class<?> clazz = Person.class;
Person p = (Person) clazz.newInstance();
p.name = "tom";
p.age = 18;
System.out.println(p);
}
private static class Person {
private String name;
private int age;
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + "]";
}
}
}
The output of above code snippet is below :
Person [name=tom, age=18]
The newInstance() method of the Class class has several constraints :
- only parameterless constructor can be invoked;
- the invoked constructor must be public;
- all checked and unchecked exceptions thrown during constructor invocation are propagated.
3.2 Creating objects using the Constructor class
From version 1.1, the java.lang.reflect package offers the Constructor class to create instances by invoking any constructor of a class.
The getDeclaredConstructor() method of the Class class makes it possible to obtain an instance of the Constructor class which encapsulates the constructor whose parameter types have been supplied to this method.
The Constructor class offers the newInstance() method which expects an Object type array as a parameter that must contain the values that will be provided when invoking the constructor.
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
public class ClassTest11 {
public static void main(String[] args) throws InstantiationException, IllegalAccessException, NoSuchMethodException,
SecurityException, IllegalArgumentException, InvocationTargetException {
Class<?> clazz = Person.class;
Constructor<?> constructor = clazz.getConstructor(String.class, int.class);
Person p = (Person) constructor.newInstance("tom", 18);
System.out.println(p);
}
private static class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + "]";
}
}
}
Below is the output of above code snippet :
Person [name=tom, age=18]
4. Dynamic invocation of a method
The Reflection API allows to dynamically invoke a method of an object.
To dynamically invoke a method of an instance, we use the invoke(Object obj, Object[] args) method of the java.lang.Method class which has several parameters :
- the first parameter is the instance on which the method should be invoked;
- the following parameters are the values that will be passed as parameters during the invocation : an arbitrary number of parameters can be passed and the supplied parameter values must respect the type and order of the method signature.
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class ClassTest12 {
public static void main(String[] args) throws NoSuchMethodException, SecurityException, IllegalAccessException,
IllegalArgumentException, InvocationTargetException {
Person p = new Person("tom", 18);
Class<?> clazz = p.getClass();
Method method = clazz.getMethod("speak", String.class);
method.invoke(p, "hello world");
}
private static class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + "]";
}
public void speak(String words) {
System.out.println(this + " : " + words);
}
}
}
The output of above code snippet is below :
Person [name=tom, age=18] : hello world
4.1 Handling an exception thrown by the invoked method
When invoking a method dynamically using the invoke() method, if an exception is thrown by the invoked method then it is chained in an exception of type java.lang.reflect.InvocationTargetException.
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class ClassTest13 {
public static void main(String[] args)
throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException {
Person p = new Person("tom", 18);
Class<?> clazz = p.getClass();
Method method = clazz.getMethod("speak", String.class);
try {
method.invoke(p, "Such a long fucking day!");
} catch (InvocationTargetException e) {
Throwable cause = e.getCause();
System.out.println(cause.getMessage());
}
}
private static class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + "]";
}
public void speak(String words) {
if (words != null && words.contains("fuck")) {
throw new IllegalStateException(this + " is saying some dirty words which is not allowed !!!");
}
System.out.println(this + " : " + words);
}
}
}
Below is the output of above code snippet :
Person [name=tom, age=18] is saying some dirty words which is not allowed !!!
4.2 Invoking a static method
If the method to invoke is static then null must be passed as the value of the first parameter which corresponds to the instance to invoke.
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class ClassTest14 {
public static void main(String[] args) throws NoSuchMethodException, SecurityException, IllegalAccessException,
IllegalArgumentException, InvocationTargetException {
Employee p1 = new Employee("tom");
System.out.println(p1);
Employee p2 = new Employee("jerry");
System.out.println(p2);
Class<?> clazz = Employee.class;
Method method = clazz.getMethod("increment", int.class);
method.invoke(null, 2);
System.out.println("Total number of employees = " + Employee.numberOfEmployees);
}
private static class Employee {
private static int numberOfEmployees;
private String name;
public Employee(String name) {
this.name = name;
}
public static void increment(int value) {
numberOfEmployees += value;
}
@Override
public String toString() {
return "Employee [name=" + name + "]";
}
}
}
The output of above code snippet is below :
Employee [name=tom]
Employee [name=jerry]
Total number of employees = 2
4.3 Access to private methods
The getMethod() and getMethods() methods only return public methods.
To obtain private methods, it has to use the getDeclaredMethod() and getDeclaredMethods() methods.
But the getDeclaredMethod() method can only access methods that are declared in the class itself, it does not allow access to methods of superclasses.
By default, the dynamic invocation of a private method throws an exception of type IllegalAccessException.
The Method class inherits from the AccessibleObject class which has the setAccessible() method.
It allows to bypass access checks and thus to access a method declared private or protected by using the Reflection API.
1import java.lang.reflect.InvocationTargetException;
2import java.lang.reflect.Method;
3
4public class ClassTest15 {
5 public static void main(String[] args)
6 throws SecurityException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException,
7 IllegalAccessException {
8 Employee e = new Employee("tom", 18, 1);
9
10 Class<?> clazz = e.getClass();
11
12 Method method = null;
13
14 try {
15 method = clazz.getDeclaredMethod("speak", String.class);
16 } catch (NoSuchMethodException ex) {
17 ex.printStackTrace();
18 }
19
20 method = clazz.getDeclaredMethod("work", null);
21
22 try {
23 method.invoke(e, "hello world");
24 } catch (IllegalAccessException ex) {
25 ex.printStackTrace();
26 }
27
28 method.setAccessible(true);
29 method.invoke(e, null);
30 }
31}
32
33class Person {
34 private String name;
35 private int age;
36
37 public Person(String name, int age) {
38 this.name = name;
39 this.age = age;
40 }
41
42 @Override
43 public String toString() {
44 return "Person [name=" + name + ", age=" + age + "]";
45 }
46
47 private void speak(String words) {
48 System.out.println(this + " : " + words);
49 }
50}
51
52class Employee extends Person {
53 private int id;
54
55 public Employee(String name, int age, int id) {
56 super(name, age);
57 this.id = id;
58 }
59
60 @Override
61 public String toString() {
62 return "Employee [id=" + id + "] " + super.toString();
63 }
64
65 private void work() {
66 System.out.println(this + " is now working...");
67 }
68}
Below is the output of above code snippet :
java.lang.NoSuchMethodException: Employee.speak(java.lang.String)
at java.base/java.lang.Class.getDeclaredMethod(Class.java:2848)
at ClassTest15.main(ClassTest15.java:15)
java.lang.IllegalAccessException: class ClassTest15 cannot access a member of class Employee with modifiers "private"
at java.base/jdk.internal.reflect.Reflection.newIllegalAccessException(Reflection.java:394)
at java.base/java.lang.reflect.AccessibleObject.checkAccess(AccessibleObject.java:714)
at java.base/java.lang.reflect.Method.invoke(Method.java:571)
at ClassTest15.main(ClassTest15.java:23)
Employee [id=1] Person [name=tom, age=18] is now working...
4.4 Invocation of a method with generic type
There are several points to consider when using reflection to invoke a method whose type of a parameter is a generic type.
In below code snippet, although the generic type of the class is Integer, the method is not found by specifying the Integer type as a parameter.
When the method type is a generic type, it must be replaced with the Object type when the Reflection API is used to invoke the method.
1import java.lang.reflect.InvocationTargetException;
2import java.lang.reflect.Method;
3
4public class ClassTest16 {
5 public static void main(String[] args)
6 throws IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException,
7 SecurityException {
8 Box<Integer> box = new Box<Integer>();
9
10 Method method = null;
11
12 try {
13 method = Box.class.getMethod("add", Integer.class);
14 } catch (NoSuchMethodException e) {
15 e.printStackTrace();
16 }
17
18 System.out.println();
19
20 method = Box.class.getMethod("add", Object.class);
21
22 method.invoke(box, Integer.valueOf(10));
23
24 System.out.println("Box's Value = " + box.get());
25 }
26
27 private static class Box<T> {
28 private T t;
29
30 public void add(T t) {
31 this.t = t;
32 }
33
34 public T get() {
35 return t;
36 }
37 }
38}
Below is the output of above code snippet :
java.lang.NoSuchMethodException: ClassTest16$Box.add(java.lang.Integer)
at java.base/java.lang.Class.getMethod(Class.java:2395)
at ClassTest16.main(ClassTest16.java:13)
Box's Value = 10