Inspecting Classes, Fields, Methods and Constructors

Naveen AnamaneniUpdated · 6 min read

Once you have a Class object, you can ask it for its members: fields, methods and constructors. Each member is represented by an object from java.lang.reflect: Field, Method or Constructor. These objects tell you the member's name, type, modifiers, parameters and annotations. In this lesson, you will learn the two families of lookup methods (with and without "Declared"), what each member object can tell you, and how to inspect records.

Two Families of Lookup Methods

The most important thing to know is the difference between these two groups:

MethodsWhich members they return
getFields(), getMethods(), getConstructors()Only public members, including inherited ones (for fields and methods)
getDeclaredFields(), getDeclaredMethods(), getDeclaredConstructors()All members declared in this class itself, private ones too, but nothing inherited

Each has a single-member version that looks up by name: getField("name"), getDeclaredMethod("name", parameterTypes...), and so on. They throw NoSuchFieldException or NoSuchMethodException when nothing matches.

What the Member Objects Tell You

ObjectUseful methods
FieldgetName(), getType(), getGenericType(), getModifiers(), get(obj), set(obj, value)
MethodgetName(), getReturnType(), getParameterTypes(), getParameters(), getModifiers(), invoke(obj, args...)
ConstructorgetParameterTypes(), getParameterCount(), getModifiers(), newInstance(args...)
All threegetAnnotation(type), isAnnotationPresent(type), getDeclaringClass()

getModifiers() returns an int with one bit per modifier. Modifier.toString(int) turns it into text, and methods such as Modifier.isPrivate(int) test single modifiers.

Example: Inspecting a Class

Let's see an example with a Student class that extends Person and has members of every visibility:

Inspect.java▶ Try it
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Parameter;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
 
public class Inspect {
    static class Person {
        public String name;
        protected int age;
 
        public String getName() {
            return name;
        }
    }
 
    static class Student extends Person {
        public static final int MAX_MARKS = 100;
        public int marks;
        private List<String> subjects;
 
        public Student() { }
 
        private Student(String name, int marks) {
            this.name = name;
            this.marks = marks;
        }
 
        public boolean passed() {
            return marks >= 35;
        }
 
        private void secret(String code, int times) { }
    }
 
    static List<String> names(Member[] members) {
        return Arrays.stream(members)
            .filter(m -> m.getDeclaringClass() != Object.class)
            .map(Member::getName)
            .sorted()
            .toList();
    }
 
    public static void main(String[] args) throws Exception {
        Class<Student> c = Student.class;
        System.out.println("getFields:          " + names(c.getFields()));
        System.out.println("getDeclaredFields:  "
            + names(c.getDeclaredFields()));
        System.out.println("getMethods:         " + names(c.getMethods()));
        System.out.println("getDeclaredMethods: "
            + names(c.getDeclaredMethods()));
 
        Field f = c.getDeclaredField("subjects");
        System.out.println(Modifier.toString(f.getModifiers()) + " | "
            + f.getType().getSimpleName() + " | " + f.getGenericType());
        Field max = c.getField("MAX_MARKS");
        System.out.println(Modifier.toString(max.getModifiers())
            + " = " + max.get(null));
 
        Method m = c.getDeclaredMethod("secret", String.class, int.class);
        System.out.println(Modifier.toString(m.getModifiers()) + " "
            + m.getReturnType() + " " + m.getName()
            + Arrays.toString(m.getParameterTypes()));
        for (Parameter p : m.getParameters()) {
            System.out.println("  parameter "
                + p.getType().getSimpleName() + " " + p.getName());
        }
 
        Constructor<?>[] cs = c.getDeclaredConstructors();
        Arrays.sort(cs,
            Comparator.comparingInt(Constructor::getParameterCount));
        for (Constructor<?> k : cs) {
            System.out.println(Modifier.toString(k.getModifiers())
                + " constructor with " + k.getParameterCount()
                + " parameter(s)");
        }
 
        try {
            c.getMethod("secret", String.class, int.class);
        } catch (NoSuchMethodException e) {
            System.out.println("NoSuchMethodException: " + e.getMessage());
        }
    }
}
Output
getFields:          [MAX_MARKS, marks, name]
getDeclaredFields:  [MAX_MARKS, marks, subjects]
getMethods:         [getName, passed]
getDeclaredMethods: [passed, secret]
private | List | java.util.List<java.lang.String>
public static final = 100
private void secret[class java.lang.String, int]
  parameter String arg0
  parameter int arg1
public constructor with 0 parameter(s)
private constructor with 2 parameter(s)
NoSuchMethodException: Inspect$Student.secret(java.lang.String,int)

Explanation:

  • getFields includes the inherited public field name but not the private subjects or the protected age. getDeclaredFields includes subjects but nothing from Person.
  • Methods follow the same pattern. (getMethods also returns the public methods of Object, such as toString; the helper filters those out, and sorts the names because the order of returned members isn't guaranteed.)
  • getType() vs getGenericType(): the first gives the erased type, List; the second keeps the declaration's type argument, List<String>. This is how libraries such as Jackson know what a list should contain.
  • max.get(null): a static field needs no object, so null is passed.
  • Looking up a method needs its name and parameter types, because of overloading. Primitive types are written as int.class.
  • getMethod("secret", ...) fails because getMethod sees only public methods.

Parameter Names: Arg0 or the Real Name?

The output shows arg0 and arg1 instead of code and times. By default, the compiler doesn't store parameter names in the class file. Compile with the -parameters option and they are kept:

Terminal
javac -parameters Inspect.java
java Inspect
Terminal output (parameter lines)
  parameter String code
  parameter int times

Spring relies on this option to match request parameters to method parameters by name, which is why Spring Boot's build setup turns it on.

Inspecting Records

A record's components are available in declaration order through getRecordComponents(), each with its accessor method:

Rec.java▶ Try it
import java.lang.reflect.RecordComponent;
 
public class Rec {
    record Student(String name, int marks) { }
 
    public static void main(String[] args) throws Exception {
        Student s = new Student("Navika", 91);
        for (RecordComponent rc : Student.class.getRecordComponents()) {
            System.out.println(rc.getType().getSimpleName() + " "
                + rc.getName() + " = " + rc.getAccessor().invoke(s));
        }
    }
}
Output
String name = Navika
int marks = 91

Explanation: getAccessor() returns the Method object for name() or marks(), and invoke(s) calls it on s. Unlike fields and methods, record components are returned in a guaranteed order, the order of the record header.

Common Mistakes

Using getMethods() and wondering where the private methods are. Use getDeclaredMethods().

Using getDeclaredFields() and missing inherited fields. Loop over getSuperclass() too.

Relying on the order of fields or methods. Sort them if order matters.

Writing Integer.class for an int parameter. The lookup needs the exact type, int.class.

Interview Corner

What is the difference between getMethods() and getDeclaredMethods()?

getMethods() returns all public methods of the class, including those inherited from superclasses and interfaces. getDeclaredMethods() returns all methods declared directly in the class, whatever their visibility (public, protected, package-private and private), but none that are inherited. The same distinction applies to getFields() and getDeclaredFields(), and to the constructor methods (constructors are never inherited).

Why does reflection show parameter names as arg0, arg1?

The compiler doesn't record parameter names in the class file by default. Compiling with javac -parameters stores them, and then Parameter.getName() returns the real names. Parameter.isNamePresent() tells which case applies.

Practice

  1. Easy: Print every method declared in java.lang.String whose name starts with "index", with its parameter types.
  2. Harder: Write allFields(Class<?> c) that returns the fields of a class and all its superclasses, then test it on Student.

Quick check: Inspecting classes

4 questions
1Which call returns private fields declared in a class?
2Does getDeclaredMethods() include inherited methods?
3How do you look up a method void pay(int amount)?
4What makes Parameter.getName() return real names?

Key Takeaways

Was this tutorial helpful?

Lesson 467 of 482