Sitemap

Reflection API in Java

Unlock the Potential of Your Java Applications with Reflection

Press enter or click to view image in full size
Photo by Juan Davila on Unsplash

Table Of Contents

  1. Introduction to Reflection.
  2. Technical Jargon used in this article.
  3. Reflection API in Java.
  4. Use Cases of Reflection.
  5. Summary of the article.

Introduction to Reflection

It examines and manipulates the class, Methods, and Fields at the runtime. Unlike the compile time analysis, reflection lets you access and modify class information during runtime. Reflection provides a way to achieve dynamic behavior that is impossible with statically typed languages like Java at compile time alone.

Technical Jargon used in this article

Runtime Configuration

It is a way of achieving adaptability and flexibility in your applications at runtime. It allows you to modify the application behavior without compiling or restarting the applications.

You might have heard of properties files in some of the applications basically, those are to load configurations at the runtime which can lead to changes in the application behavior without restarting the application.

Dynamic Class Loading

From the definition, it is clear that it is related to the loading of a class at runtime instead of during the initial compilation phase. For example, in Java ClassLoader mechanism is responsible for loading the class on demand.

Relationship Between Runtime Configuration and Dynamic Class Loading

Runtime configuration can be used in conjunction with dynamic class loading. Configuration files can specify which classes to load or how to configure them dynamically.

One question you should have been asked here is, How we can load the class(Dynamic class loading) specified in the Runtime Configuration?

yes, you are correct with the help of Reflection API.

Reflection API in Java

As we discussed Reflection is used to load the class at runtime, Let's see an example.

There are 3 ways to load a class during the runtime.

public class TestSuite {
public static void main(String[] args) throws ClassNotFoundException {

// Load the class "org.example.Reflection.test", change as per you class name

// 1. using forName
Class<?> forNameObj = Class.forName("org.example.Reflection.TestCases");
System.out.println("Loaded class: " + forNameObj.getName());

// 2. using getClass
TestCases testObj = new TestCases() ;
Class<?> getClObj = testObj.getClass() ;
System.out.println("Loaded class: "+getClObj.getName());

// 3. using .class
Class<?> dotClsObj = TestCases.class ;
System.out.println("Loaded class: " + dotClsObj.getName());

int modifier = getClObj.getModifiers() ;
// type caste into string
System.out.println(Modifier.toString(modifier));

Constructor<?> constructor[] = getClObj.getConstructors() ;

System.out.println("Count of constructor " + constructor.length);
for(Constructor c : constructor ){
System.out.println(c.getName());
System.out.println(Modifier.toString(c.getModifiers()));
}
}

Java provides a lot of Reflection APIs which help us to manipulate and access the class details during runtime like —
getName(), getParameterCount(), getParameterTypes(), getReturnType(), getModifiers(), so.on

Get Abhishek Kumar’s stories in your inbox

Join Medium for free to get updates from this writer.

We will design 2 classes TestSuite and TestCases, We will use TestSuite to test the Reflection API on class TestCases.

TestCases.java

package org.example.Reflection;

public class TestCases {
public TestCases(){}
public TestCases(int a, int b){}
public TestCases(int a){}
public TestCases(int a, int b, int d){}
public int unitTestCaseCount ;
private int failedTestCasesCount ;
public static void main( String[] args) throws ClassNotFoundException {
}

public void method2(int a, int b){}
public void method3(){}
public void method4(int a){}
}

Let's look at some of the above-mentioned APIs in the code.

Class Method API

package org.example.Reflection;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;

public class TestSuite {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {

TestCases TcObj = new TestCases() ;
Class<?> obj = TcObj.getClass() ;

Method methods[] = obj.getMethods() ;
for(Method m : methods){
System.out.println("Method Name: " + m.getName());
System.out.println("Modifier: " + Modifier.toString(m.getModifiers()));
System.out.println("Return type: " + m.getReturnType());
System.out.println("Parameters count: " + m.getParameterCount());
}
}
}

Explanation:

  1. getName()- As m is a method getName returns the name of the method m.
  2. getModifiers()- Return the int corresponding to the modifier of method m and Modifier.toString() — Type cast the int into access modifiers name.
  3. getReturnType()- Gives the return type of the method m.
  4. getParameterCount()- Return the number of parameters.

Class Constructor API

package org.example.Reflection;

import java.lang.reflect.*;

public class TestSuite {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {

TestCases TcObj = new TestCases() ;
Class<?> obj = TcObj.getClass() ;

Constructor<?> constructor[] = obj.getConstructors() ;
System.out.println("Count of constructor " + constructor.length);
for(Constructor<?> cnstr : constructor ){
System.out.println("Name of Constructor: " + cnstr.getName());
System.out.println("Modifier: " + Modifier.toString(cnstr.getModifiers()));
System.out.println("Parameter Count = " + cnstr.getParameterCount());

Parameter params[] = cnstr.getParameters() ;
int pos = 0;
for(Parameter p : params ){
pos += 1;
System.out.println("Parameter" + pos +": " + p.getName() + "of constructor " + cnstr.getName());
System.out.println(p.getType());
System.out.println(p.getModifiers());
}
}

}
}

Explanation:

  1. getName()- Returns the name of the constructor.
  2. getModifiers()- Return the int corresponding to the modifier of the constructor and Modifier.toString() — Type cast the int into the access modifier name.
  3. getParameterCount()- Return the number of parameters.

Class Field API

package org.example.Reflection;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;

public class TestSuite {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {

TestCases TcObj = new TestCases() ;
Class<?> obj = TcObj.getClass() ;

// find the field with name = unitTestCaseCount
Field UTCCount = obj.getField("unitTestCaseCount") ;
String fieldName = UTCCount.getName() ;
System.out.println("Field name: :" + fieldName);


UTCCount.setInt(TcObj, 10);

// get the count
int count = UTCCount.getInt(TcObj) ;
System.out.println("value of " + fieldName + " of class " + obj.getName() + " = " + count);

//Access modifier
System.out.println("Modifier: " + Modifier.toString(UTCCount.getModifiers())) ;

// Field type
System.out.println("Data type of " + fieldName + " is " + UTCCount.getType());

// Get the hash code for field
System.out.println("Hash Code for " + fieldName + " is " + UTCCount.hashCode());

// accessing the private member
Field FTCCount = obj.getDeclaredField("failedTestCasesCount") ; // getField will not work for private member
FTCCount.setAccessible(true);
FTCCount.setInt(TcObj, 2);

// access the private member
System.out.println(FTCCount.getInt(TcObj));
}
}

Explanation:

  1. getDeclaredField() — returns the private Field object referring to failedTestCasesCount.
  2. setAccessible() — set the accessibility to access the private field.
  3. setInt ()— set the value for the private field.

Use Cases of Reflection

1. Runtime Configuration and Dynamic Class Loading

  • Applications can leverage reflection to adapt to changing configurations at runtime.
  • Imagine a system that can load custom logging implementations based on configuration settings. Reflection allows for discovering available logging classes and dynamically creating instances to handle logging tasks.

2. Unit Testing Frameworks:

  • Testing frameworks like JUnit (though less reliant on reflection now) used reflection to discover methods annotated with @Test.
  • By inspecting classes and methods through reflection, they could automatically identify and execute test cases within the codebase.

Let’s understand the Unit Testing with the reflection.

Scenario:

Imagine you have a class MathUtils with a private helper method add that performs addition. You want to write a test case for add but can't directly access it due to its private nature.

public class MathUtilsTest {

@Test
public void testAddUsingReflection() throws Exception {
// Get the MathUtils class object
Class<?> mathUtilsClass = MathUtils.class;

// Get the private add method using reflection
Method addMethod = mathUtilsClass.getDeclaredMethod("add", int.class, int.class);

// Make the add method accessible (required for private methods)
addMethod.setAccessible(true);

// Create an instance of MathUtils (optional if the method is static)
MathUtils mathUtils = new MathUtils();

// Invoke the add method with reflection and arguments
int result = (int) addMethod.invoke(mathUtils, 5, 3);

// Use JUnit assertions to verify the expected outcome
assertEquals(8, result);
}
}

Explanation:

  1. The test retrieves the MathUtils class object using Class.class.
  2. It uses reflection to get private add method using getDeclaredMethod.
  3. Since add is private, setAccessible(true) is used to make it accessible for testing.
  4. An instance of MathUtils is created (optional if add is static).
  5. The invoke method on the addMethod object is used to call the private method with arguments (5 and 3 in this case). The result is cast into an int.
  6. Finally, an assertion verifies if the result is indeed 8.

3. Generic Code and Data Serialization/Deserialization:

  • Libraries like Gson or Jackson for JSON parsing utilize reflection to automatically convert Java objects to and from JSON format.
  • Reflection helps them introspect class structure, access fields, and their types, and build corresponding JSON representations. This simplifies data serialization/deserialization for various object types.

Summary of the Article

  1. Reflection gives us the flexibility to manipulate the class and method at runtime.
  2. There are 3 ways to load the class- getClass(), forName(), class
  3. Frequent used API — getName(), getModifiers(), getReturnType(), getParameterCount(), getMethod(), getDeclaredMethod().
  4. We can access private members using getDeclaredMethod() and by setting the Field accessibility to True using setAccessible(true).
  5. Use Case of Reflection — Unit Testing, Runtime Configuration & Dynamic Class Loading, Generic Code and Data Serialization/Deserialization.

Thank you for reading this article till the end, I hope it added some value to your skills. Please do consider liking and Sharing your feedback.

Software Engineer at OpenText India, Loves to write articles on the technology related to JAVA, Spring Boot, Solidity, RESTful API, Spring Security.