Inspecting Classes, Fields, Methods and Constructors
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:
| Methods | Which 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
| Object | Useful methods |
|---|---|
Field | getName(), getType(), getGenericType(), getModifiers(), get(obj), set(obj, value) |
Method | getName(), getReturnType(), getParameterTypes(), getParameters(), getModifiers(), invoke(obj, args...) |
Constructor | getParameterTypes(), getParameterCount(), getModifiers(), newInstance(args...) |
| All three | getAnnotation(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:
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());
}
}
}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:
getFieldsincludes the inherited public fieldnamebut not the privatesubjectsor the protectedage.getDeclaredFieldsincludessubjectsbut nothing fromPerson.- Methods follow the same pattern. (
getMethodsalso returns the public methods ofObject, such astoString; the helper filters those out, and sorts the names because the order of returned members isn't guaranteed.) getType()vsgetGenericType(): 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, sonullis passed.- Looking up a method needs its name and parameter types, because of overloading. Primitive types are written as
int.class. getMethod("secret", ...)fails becausegetMethodsees 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:
javac -parameters Inspect.java
java Inspect parameter String code
parameter int timesSpring 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:
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));
}
}
}String name = Navika
int marks = 91Explanation: 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
- Easy: Print every method declared in
java.lang.Stringwhose name starts with"index", with its parameter types. - Harder: Write
allFields(Class<?> c)that returns the fields of a class and all its superclasses, then test it onStudent.
Key Takeaways
Was this tutorial helpful?