Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

Reflection

Reflection is a program's ability to learn the structure of its classes while running — what fields, methods, constructors and annotations they have — and to use them knowing only their names. Ordinary code hardly needs reflection, but frameworks are built on it: Spring creates objects, JUnit finds tests, JSON libraries read object fields.

The Class object

Everything starts with a Class object — the description of a class. You can get it from any object with getClass() or directly from the class name with .class:

void main() {
    String text = "Hello";
 
    Class<?> type = text.getClass();
    IO.println(type.getSimpleName());
    IO.println(type.getName());
    IO.println(type == String.class);
}

Output:

String
java.lang.String
true

getSimpleName() returns the short name and getName() the full one, including the package. There is exactly one Class object per class, so they can be compared with ==.

Fields and methods

getDeclaredFields() and getDeclaredMethods() return the fields and methods declared in the class itself, private ones included. Their order isn't guaranteed, so we sort them by name for printing:

class User {
    private String name;
    private int age;
 
    public String greet() {
        return "Hello, " + name;
    }
 
    public int getAge() {
        return age;
    }
}
 
void main() {
    for (Field field : User.class.getDeclaredFields()) {
        IO.println("Field: " + field.getType().getSimpleName() + " " + field.getName());
    }
 
    Arrays.stream(User.class.getDeclaredMethods())
            .map(Method::getName)
            .sorted()
            .forEach(name -> IO.println("Method: " + name));
}

Output (the order of the fields may differ):

Field: String name
Field: int age
Method: getAge
Method: greet

Calling a method by name

A method can be found by its name and parameter types and then called with invoke. The first argument of invoke is the object to call the method on, the rest are the arguments:

class Calculator {
    public int add(int a, int b) {
        return a + b;
    }
 
    public int multiply(int a, int b) {
        return a * b;
    }
}
 
void main() throws Exception {
    Calculator calc = new Calculator();
 
    for (String operation : List.of("add", "multiply")) {
        Method method = Calculator.class.getMethod(operation, int.class, int.class);
        Object result = method.invoke(calc, 6, 7);
        IO.println(operation + " = " + result);
    }
}

Output:

add = 13
multiply = 42

The method name here is an ordinary string. It could be read from a settings file or a user request — this is how web framework routers work.

Creating an object

An object can also be created knowing only its class: getDeclaredConstructor finds a constructor by its parameter types and newInstance calls it with arguments:

record Report(String title) {
    Report {
        IO.println("Report created: " + title);
    }
}
 
void main() throws Exception {
    Class<?> type = Report.class;
    Object report = type.getDeclaredConstructor(String.class).newInstance("Sales");
 
    IO.println(report);
}

Output:

Report created: Sales
Report[title=Sales]

Record components

Records have a getRecordComponents() method that returns the components in declaration order. With it you can easily write, say, a simple conversion of a record into "key=value" text:

record Product(String name, double price, int stock) {}
 
String describe(Record value) throws Exception {
    List<String> parts = new ArrayList<>();
    for (RecordComponent component : value.getClass().getRecordComponents()) {
        Object fieldValue = component.getAccessor().invoke(value);
        parts.add(component.getName() + "=" + fieldValue);
    }
    return String.join("; ", parts);
}
 
void main() throws Exception {
    IO.println(describe(new Product("Tea", 4.5, 120)));
}

Output:

name=Tea; price=4.5; stock=120

getAccessor() returns the component's accessor method — the very name() or price(). Libraries that turn records into JSON work this way.

Common mistake

The getMethod method finds only public methods. A method without an access modifier won't be found, and an exception is thrown:

class Greeter {
    String greet(String name) {
        return "Hello, " + name;
    }
}
 
void main() throws Exception {
    Method method = Greeter.class.getMethod("greet", String.class);
    IO.println(method.invoke(new Greeter(), "Anna"));
}

Output:

Exception in thread "main" java.lang.NoSuchMethodException: Main$Greeter.greet(java.lang.String)
	at Main.main(Main.java:8)

For any method of a class, private ones included, use getDeclaredMethod. And remember: reflection bypasses the compiler's checks — you'll see a typo in a method name only at run time. So in ordinary code, call methods directly.

Thanks for reading!

Donate to the site — we work hard for you.