Skip to content

Basics of reflection

Introduction

Reflection is a computer science term that denotes the process by which a computer program can be modified at runtime in a manner dependent on its own code and behavior at runtime. The programming paradigm closely related to the reflection mechanism is called reflective programming. Reflection allows you to easily manage code as if it were data. It is most often used to change the standard behavior of already defined methods or functions, and to create your own semantic constructs that modify the language. On the other hand, code that uses reflection is less readable and does not allow for syntactic and semantic validation during compilation, so error tracking can be cumbersome. This mechanism is more common in high-level languages, usually based on a virtual machine.

In Java, the reflection mechanism is implemented with the help of objects available in the java.lang.reflect package.

Java Reflection

Reflection API allows you to share and manipulate:

  • classes
  • constructors
  • methods
  • class fields.

Below, the indicated mechanisms for the Reflection API will be discussed. Source codes will be based on the following class definition as defined in the pl.sdacademy package:

public class Car {
    private boolean isPrototype = true;
    private String name;
    private String model;

    public Car() {
    }

    public Car(String name, String model) {
        this.name = name;
        this.model = model;
    }

    public Car(String name, String model, boolean isPrototype) {
        this.name = name;
        this.model = model;
        this.isPrototype = isPrototype;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getModel() {
        return model;
    }

    public void setModel(String model) {
        this.model = model;
    }

    private boolean isPrototype() {
        return isPrototype;
    }

    @Override
    public String toString() {
        return "Car{" +
                "isPrototype=" + isPrototype +
                ", name='" + name + '\'' +
                ", model='" + model + '\'' +
                '}';
    }
}

Class

In Java, each type is a reference (e.g. classes, enums, arrays, interfaces) or a primitive.

For each object type, the JVM creates an instance of java.lang.Class, which provides methods to manipulate and retrieve the object's runtime properties, including its members and type information. This class also provides the ability to create new classes and objects. Most importantly, it is the entry point for all Reflection APIs. The example below shows how to "examine" a predefined Car class while the program is running.

Class<?> carClass = Class.forName("pl.sdacademy.Car");
Method[] methods = carClass.getDeclaredMethods();
Field[] fields = carClass.getDeclaredFields();
System.out.println("Available methods: ");
for (Method method : methods) {
  System.out.println(method);
}

System.out.println("Available fields: ");
for (Field field : fields) {
  System.out.println(field);
}

Execution of the program above will display the following lines on the screen:

Available methods: 
public java.lang.String pl.sdacademy.Car.toString()
public java.lang.String pl.sdacademy.Car.getName()
public void pl.sdacademy.Car.setName(java.lang.String)
public void pl.sdacademy.Car.setModel(java.lang.String)
public java.lang.String pl.sdacademy.Car.getModel()
private boolean pl.sdacademy.Car.isPrototype()
Available fields: 
private boolean pl.sdacademy.Car.isPrototype
private java.lang.String pl.sdacademy.Car.name
private java.lang.String pl.sdacademy.Car.model

Method

The methods return values, pass parameters, and can throw exceptions. The java.lang.reflect.Method class provides methods for getting information about the type of parameters and the return value. It can also be used to call methods on a given object. The example below shows how to call the appropriate setters and getters (and thus methods) using reflection:

Class<?> carClass = Class.forName("pl.sdacademy.Car");                        // getting the Class object for the class Car
Car car = (Car) carClass.newInstance();                                       // (1)
Method setNameMethod = carClass.getDeclaredMethod("setName", String.class);   // (2)
Method setModelMethod = carClass.getDeclaredMethod("setModel", String.class); // (3)
Method getNameMethod = carClass.getDeclaredMethod("getName");                 // (4)
setNameMethod.invoke(car, "Porsche");                                         // (5)
setModelMethod.invoke(car, "K1");                                             // (6)
System.out.println("Get name: " + getNameMethod.invoke(car));                 // (7)
System.out.println("Use method using reflection: ");                          // (8)
System.out.println(car);                                                      // (9)

The above example on the line:

  • (1) - creates a new object instance using a method marked as @ Deprecated. We'll come back to creating objects correctly by means of reflection.
  • (2), (3) - using the Class object on thecarClass instance, we retrieve the Method object representing the method namedsetName and setModel, respectively, having one input argument of typeString.
  • (4) - again using reflection we get the Method object, this time for an argumentless getter namedgetName.
  • (5), (6) - we call the previously downloaded setters with the appropriate input arguments ("Porsche" and "K1"). We have to call these methods on a certain object. They are invoked on the car instance created in(1).
  • (7), (8), (9) - print the results of our calls to the screen, i.e. Get name: Porsche,Use method using reflection:iCar {isPrototype = true, name = 'Porsche', model = 'K1'}.

Field

In Java, fields have access modifiers, type, and value. The java.lang.reflect.Field class provides methods to access information about type, value and provides a set of operations for changing a specific value, even one with a private access modifier. To set the value of a field with the private access modifier, we must first call thesetAccessible method on that field with the argument true.

Another example shows how we can set private field values in a previously created Car class:

Car car = new Car();
Field field = Car.class.getDeclaredField("name");
field.setAccessible(true);
field.set(car, "test");
Field modelField = Car.class.getDeclaredField("model");
modelField.setAccessible(true);
modelField.set(car, "BMW");
System.out.println("Set field using reflection: " + car);

The above program will display the following String on the screen: * Set field using reflection: Car {isPrototype = true, name = 'test', model = 'BMW'} *.

Constructor

The set of operations for constructors in the Reflection API is defined in java.lang.reflect.Constructor and is similar to the mechanisms implemented byjava.lang.reflect.Method, with two major exceptions:

  • constructors have no return values
  • calling the constructor creates a new object instance for a given class.

The following example shows how to create a new instance of the Car object using a binary constructor:

Car car = Car.class.getConstructor(String.class, String.class).newInstance("param1", "param2");
System.out.println("Create object using reflection: " + car);

This program prints: * Create object using reflection: Car {isPrototype = true, name = 'param1', model = 'param2'} *.

Reflections and inheritance

When retrieving the Class object for a certain class, and then retrieving the list of available fields, methods or constructors, we have a choice of two groups of methods intended for this:

  • containing the word Declared in the name - return a list of fields, methods or constructors with any access modifier defined in theClass object with which we are currently working
  • not containing the word Declared in the name - they return a list of public fields, methods or constructors across the hierarchy of classes or interfaces.

The following examples show these differences based on the methods available:

final Method[] methods = Car.class.getMethods();
for (final Method method : methods) {
  System.out.println(method);
}

The above example will show us a list of the public methods available in the Car class, but also in the classes it inherits from (in this case,Object). On the screen we will see:

public java.lang.String pl.sdacademy.Car.toString()
public java.lang.String pl.sdacademy.Car.getName()
public void pl.sdacademy.Car.setName(java.lang.String)
public java.lang.String pl.sdacademy.Car.getModel()
public void pl.sdacademy.Car.setModel(java.lang.String)
public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
public final void java.lang.Object.wait() throws java.lang.InterruptedException
public boolean java.lang.Object.equals(java.lang.Object)
public native int java.lang.Object.hashCode()
public final native java.lang.Class java.lang.Object.getClass()
public final native void java.lang.Object.notify()
public final native void java.lang.Object.notifyAll()

The next piece of code will show us only the methods declared directly in the Car class:

final Method[] methods = Car.class.getDeclaredMethods();
for (final Method method : methods) {
  System.out.println(method);
}

On the screen, in addition to the public methods, we also see the private method isPrototype:

public java.lang.String pl.sdacademy.Car.toString()
public java.lang.String pl.sdacademy.Car.getName()
public void pl.sdacademy.Car.setName(java.lang.String)
private boolean pl.sdacademy.Car.isPrototype()
public java.lang.String pl.sdacademy.Car.getModel()
public void pl.sdacademy.Car.setModel(java.lang.String)