Java Reflection Basics
Reflection is a powerful feature in Java programming language that allows a programmer to examine or modify the structure of a class at runtime. That means a program can inspect and manipulate its own code, making reflection a useful tool for runtime code generation, testing, and more.
You can imagine it as something like black magic because it's possible to break several design principles using it. Most importantly, you can bypass encapsulation by accessing member fields that are not exposed by its public API. In other words, it allows getting implementation details.
java.lang.reflect package
Java Reflection is implemented by the java.lang.reflect
package. Although java.lang.reflect
package includes many interfaces, classes, and exceptions, there are only four classes that you need to know at this level. These classes are:
- Field: you can use it to get as well as modify name, value, datatype, and access modifier of a variable.
- Method: you can use it to get as well as modify name, return type, parameter types, access modifier, and exception type of a method.
- Constructor: you can use it to get as well as modify name, parameter types, and access modifier of a constructor.
- Modifier: you can use it to get information about a particular access modifier.
java.lang.Class
There is another important point. You can't just achieve reflection only with the Reflect package that we've mentioned above. The Reflect package can give you information about a field, method or constructor of a class, but first you have to take the field list, method list, and constructor list.
This is possible with the java.lang.Class
class and its static forName()
method. When you pass the name of any class to the forName()
method, it returns a Class
object that includes information about this class.
Another method which returns Class
object is getSuperclass()
instance method. This method returns a superclass of a Class
instance.
The java.lang.Class
also has several methods that you can use to get attributes (fields, methods, constructors) of the particular class you passed to forName()
method. Here are some of those methods:
getConstructors()
getDeclaredConstructors()
getFields()
getDeclaredFields()
getMethods()
getDeclaredMethods()
There are two important things to know about these methods.
First, each of these methods returns an array of objects from java.lang.reflect
classes. For example, getFields()
returns an array of objects from the java.lang.reflect.Field
class. After that, you can use methods from java.lang.reflect
package to get further information about constructors, fields, and methods.
Second, getConstructors()
, getFields()
, and getMethods()
return only public constructors, fields, and methods from the class represented by the Class
object. These methods also return inherited public fields and methods from superclasses.
Similarly,getDeclaredConstructors()
, getDeclaredFields()
, getDeclaredMethods()
return all the constructors, fields, and methods from the class represented by the Class
object. These methods don't return inherited fields and methods from superclasses.
Usually, you can see developers use declared methods more often than non-declared methods. You will understand these things better with a practical example — let's take a look at one now!
Coding examples
Suppose that you have a class called Student
. It has three public fields, one protected field, and one private field. It also has a default constructor and a public constructor. The Student
class also has a private method and a public method.
public class Student {
public String firstName;
public String lastName;
public int age;
protected String phoneNumber;
private String accountNumber;
Student(){
System.out.println("This is default Constructor");
}
public Student(String firstName, String lastName){
this.firstName= firstName;
this.lastName= lastName;
System.out.println("This is public Constructor");
}
private String sanitizeAccountNumber(String accountNumber){
System.out.println("This is a private method to sanitize account number");
//code to sanitize accountNumber goes here.
return accountNumber;
}
public void setAccountNumber(String accountNumber){
accountNumber = sanitizeAccountNumber(accountNumber);
this.accountNumber = accountNumber;
}
}
The reflection process usually has three steps:
1. Get a java.lang.Class
object of the class using the forName
approach. In this case, the class we want to reflect is Student
.
Class student = Class.forName("Student");
2. Get the attributes of classes. In this case, we are interested in superclass, fields, constructors, and methods.
Class superclass = student.getSuperclass();
Constructor[] declaredConstructors = student.getDeclaredConstructors();
Constructor[] constructors = student.getConstructors();
Field[] declaredFields = student.getDeclaredFields();
Field[] fields = student.getFields();
Method[] declaredMethods = student.getDeclaredMethods();
Method[] methods = student.getMethods();
3. Get the information about class attributes and use it. In this case, we are going to retrieve the names of superclass, constructors, fields, and methods and print them.
System.out.println("Superclass " + superclass);
for (Constructor dc : declaredConstructors) {
System.out.println("Declared Constructor " + dc.getName());
}
for (Constructor c : constructors) {
System.out.println("Constructor " + c.getName());
}
for (Field df : declaredFields) {
System.out.println("Declared Field " + df.getName());
}
for (Field f : fields) {
System.out.println("Field " + f.getName());
}
for (Method dm : declaredMethods) {
System.out.println("Declared Method " + dm.getName());
}
for (Method m : methods) {
System.out.println("Method " + m.getName());
}
You can write these three sections inside the main()
method and run this code.
Explaining the output
When you run the code block above, you will get a superclass name, lists of constructors, fields, and methods:
Superclass class java.lang.Object
Declared Constructor Student
Declared Constructor Student
Constructor Student
Declared Field firstName
Declared Field lastName
Declared Field age
Declared Field phoneNumber
Declared Field accountNumber
Field firstName
Field lastName
Field age
Declared Method sanitizeAccountNumber
Declared Method setAccountNumber
Method setAccountNumber
Method wait
Method wait
Method wait
Method equals
Method toString
Method hashCode
Method getClass
Method notify
Method notifyAll
You can see that getDeclaredConstructors()
has returned both constructors of the Student
class, while getConstructors()
has returned only the public constructor. Likewise, getDeclaredFields()
has returned all the fields of the Student
class, while getFields()
has returned only public fields.
Finally, we print the methods of the Student
class. As expected, getDeclaredMethods()
has returned both methods. Now the interesting part is that getMethods()
has returned some methods other than the expected setAccountNumber()
method. If you remember, in one of our previous topics, we mentioned that the java.lang.Object
class is the superclass of all the classes we create. The Object
class has nine public methods, and all classes we create inherit those methods. That's why you can see nine extra methods in the output.
Risks and downsides
While reflection offers powerful capabilities, it should be used with caution. Use it only when necessary, and when its benefits outweigh its potential drawbacks, which are:
- Performance overhead: Reflection can add significant overhead to your program, requiring extra processing to inspect and manipulate the code structure at runtime. This can lead to slower performance, especially in large and complex applications.
- Maintenance issues: reflection can complicate maintaining code, as the code's behavior can change dynamically at runtime. This can make it challenging to understand how the code is being used and manipulated, leading to compatibility issues and making it harder to update or change the code in the future.
To minimize these risks, we recommend keeping reflection limited to specific, well-defined areas of the code, ideally, within frameworks and libraries, where it can be used to provide a high level of abstraction and make the code more flexible and reusable.
Conclusion
Reflection is a way to get information about or modify fields, methods, and constructors of a class. The java.lang.reflect
package and the java.lang.Class
class are essential in Java reflection.
There are three steps in the Java reflection process:
- Get the
Class
object of the class that you want to reflect on. - Get the attributes of the class you want to reflect on as a list or array using
java.lang.Class
methods. - Get information about the particular attribute you got during the second step using the
java.lang.reflect
package.
Reflection is an advanced concept that requires some knowledge of JVM (Java Virtual Machine) and Java internal processes.