java.lang.reflect.Field class

The Field class provides information about and dynamic access to a single field of a class or interface. Field also allows a widening type conversion during a get or set access operation, but throws an IllegalArgumentException if narrowing would occur.

To get a Field object, we'll first write a class:


public class Person {
    private String name;
    private int age;
    
    public boolean isMale;
    
    protected String address;
    
    public static final int MAX_AGE = 120;
    public static final int MIN_AGE = 0;
}

And here's our code for working with that class:


public class Main {
    public static void main(String[] args) {
        Field[] fields = Person.class.getDeclaredFields();
        List<Field> actualFields = getFieldNames(fields);
        System.out.println(actualFields);
    }

    static List<Field> getFieldNames(Field[] fields) {
        return List.of(fields);
    }
}

This is how we get the list of the fields of our class, which we will work with later. Here's the result:

[private java.lang.String com.company.Person.name, private int com.company.Person.age, public boolean com.company.Person.isMale, protected java.lang.String com.company.Person.address, public static final int com.company.Person.MAX_AGE, public static final int com.company.Person.MIN_AGE]

Now let's figure out what we can do with this data. Let's talk about the methods of the Field class:

Method Description
getType() Returns a Class object that identifies the declared type of the field represented by this Field object.
getAnnotatedType() Returns an AnnotatedType object that represents the use of a type to specify the declared type of the field represented by this Field.
getGenericType() Returns a Type object that represents the declared type of the field represented by this Field object.
getName() Returns the name of the field represented by this Field object.
getModifiers() Returns an int encoding the Java language modifiers for the field represented by this Field object.
getAnnotations() Returns the annotations for this field. If there are no annotations, it returns an empty array.

getType(), getName(), and getModifiers() methods

We can use the getType() method to can get the type of our field. Let's write a method:


static void printTypes(List<Field> fields){
      fields.forEach(e -> System.out.println(e.getType()));
  }

And we get this result:

class java.lang.String
int
boolean
class java.lang.String
int
int

Now let's add a method to our class to get the name of a field. This will make it easier to navigate the fields of our class.


static void printTypesAndNames(List<Field> fields){
   fields.forEach(e -> System.out.printf("Field type - %s\nField name - %s\n\n", e.getType(), e.getName()));
}

Now the result is more understandable:

Field type - class java.lang.String
Field name - name

Field type - int
Field name - age

Field type - boolean
Field name - isMale

Field type - class java.lang.String
Field name - address

Field type - int
Field name - MAX_AGE

Field type - int
Field name - MIN_AGE

Great! Let's modify our method some more! We'll add access modifiers here


static void printFieldInfo(List<Field> fields){
   fields.forEach(e -> System.out.printf("Field type - %s\nField name - %s\nModifiers - %s\n\n", e.getType(), e.getName(), Modifier.toString(e.getModifiers())));
}

And let's look at what e.getModifiers() returns. This method returns an int that lets us determine the state our field's access modifiers. The Modifier class contains static variables responsible for each specific modifier of the field.

Let's wrap our return value in Modifier.toString() and immediately get its value as text:

Field type - class java.lang.String
Field name - name
Modifiers - private

Field type - int
Field name - age
Modifiers - private

Field type - boolean
Field name - isMale
Modifiers - public

Field type - class java.lang.String
Field name - address
Modifiers - protected

Field type - int
Field name - MAX_AGE
Modifiers - public static final

Field type - int
Field name - MIN_AGE
Modifiers - public static final

Here's what it looks like without Modifier.toString():

Field type - class java.lang.String
Field name - name
Modifiers - 2

Field type - int
Field name - age
Modifiers - 2

Field type - boolean
Field name - isMale
Modifiers - 1

Field type - class java.lang.String
Field name - address
Modifiers - 4

Field type - int
Field name - MAX_AGE
Modifiers - 25

Field type - int
Field name - MIN_AGE
Modifiers - 25

getAnnotations(), getAnnotatedType(), and getGenericType() methods

Let's modify the Person class to work with these methods. We'll write our own annotation and apply it to our fields. And we'll add a few more fields.

Let's create two annotations. We will pass the variable name in Pig Latin to one, and we will use the second for elements:


@Target(value=ElementType.FIELD)
@Retention(value= RetentionPolicy.RUNTIME)
public @interface Name {
    String name();
}

@Target({ ElementType.TYPE_USE })
@Retention(RetentionPolicy.RUNTIME)
public @interface Number {
}

And we'll change our main class and the Person class:


public class Person {
    @Name(name = "Ame-nay")
    private String name;

    @Name(name = "User nicknames")
    List<String> nicknames;

    private final Class<Object> type;

    private int @Number[] number;

    public Person(Class<Object> type) {
        this.type = type;
    }
}

public static void main(String[] args) {
    Field[] fields = Person.class.getDeclaredFields();
    List<Field> actualFields = getFieldNames(fields);
    
    printAdditionalInfo(actualFields);
}

static void printAdditionalInfo(List<Field> fields) {
   System.out.println("\ngetAnnotatedType:");
   fields.forEach(e -> System.out.println(e.getAnnotatedType()));

   System.out.println("\ngetGenericType:");
   fields.forEach(e -> System.out.println(e.getGenericType()));

   System.out.println("\ngetAnnotations:");
   fields.forEach(e -> System.out.println(Arrays.toString(e.getAnnotations())));
}

It's time to look at the results of our methods and figure out what they are for:

getAnnotatedType:
java.lang.Class<java.lang.Object>
java.util.List<java.lang.String>
java.lang.String
int @Number()[]

getGenericType:
java.lang.Class<java.lang.Object>
java.util.List<java.lang.String>
class java.lang.String
class [I

getAnnotations:
[]
[@Name(name="\u0055\u0073\u0065\u0072\u0020\u006e\u0069\u0063\u006b\u006e\u0061\u006d\u0065\u0073")]
[@Name(name="\u0041\u006d\u0065\u002d\u006e\u0061\u0079")]
[]
  • getAnnotatedType returns the annotation for the given field, if any. We get the annotation for the field and we can see it perfectly.

  • getGenericType lets you correctly display a parameterized type.

  • getAnnotations returns the annotations applied to our object.

This is how we can easily get all the data about each field in our class, as well as its access modifiers, annotations and data types.

java.lang.reflect.Method class

Super! We've talked about the fields of our class. Now it's time to talk about the methods.

To get a Method object, we call the getMethod method, passing it the name of our method. This is the basic way to get a Method object:


Method getNameMethod =  Person.class.getMethod("getName");

We'll continue to work with our class. Let's add getters and setters, and hashCode, equals and toString methods:


public class Person {
    private String name;
    private int age;

    public boolean isMale;

    protected String address;

    public static final int MAX_AGE = 120;
    public static final int MIN_AGE = 0;

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public boolean isMale() {
        return isMale;
    }

    public void setMale(boolean male) {
        isMale = male;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", isMale=" + isMale +
                ", address='" + address + '\'' +
                '}';
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Person person = (Person) o;
        return age == person.age && isMale == person.isMale && Objects.equals(name, person.name) && Objects.equals(address, person.address);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, age, isMale, address);
    }
}

Now let's prepare a set of methods that we will examine using the Method class. Here is a list of the most important methods:

Method Description
getName() Returns the name of the method.
getModifiers() Returns the access modifier of this method.
getReturnType() Returns the return type of the method.
getGenericReturnType() Returns the return type of the method, accounting for generic methods.
getParameterTypes() Returns an array of method parameters.
getGenericParameterTypes() Returns an array of method parameters, accounting for generic methods.
getExceptionTypes() Returns the exceptions that the method can throw.
getGenericExceptionTypes() Returns the exceptions that the method can throw, accounting for parameterized types.
getAnnotations() Returns the annotations for the method, including parent annotations.
getDeclaredAnnotations() Returns the annotations for the method, excluding parent annotations.

To get an array of the methods of our class will, we can call this method:


Method[] methods = Person.class.getDeclaredMethods();

It will give us all the methods in our class.

getName() and getModifiers() methods

We can use getName to get the name of each method:


static List<String> getMethodsName(Method[] methods) {
    return Arrays.asList(methods)
            .stream()
            .map(Method::getName)
            .collect(ArrayList::new, ArrayList::add, ArrayList::addAll);
}

Now to get the modifiers, let's write a method that uses getModifiers:


static List<String> getModifiers(Method[] methods) {
    return Arrays
            .stream(methods)
            .map(Method::getModifiers)
            .map(String::valueOf)
            .collect(ArrayList::new, ArrayList::add, ArrayList::addAll);
}

Here's our main method:


public static void main(String[] args) {
    Method[] methods = Person.class.getDeclaredMethods();

    System.out.println(getMethodsName(methods));
    System.out.println(getModifiers(methods));
}

Our result:

[getName, equals, toString, hashCode, setName, getAddress, isMale, getAge, setAge, setMale, setAddress]
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]

All of our methods have the public modifier, so the last method returns an array of ones. If we modify our code, we will see our the modifiers themselves:


public static void main(String[] args) {
    Method[] methods = Person.class.getDeclaredMethods();

    System.out.println(getMethodsName(methods));
    System.out.println(modifyModifiers(getModifiers(methods)));
}
[getName, equals, toString, hashCode, setName, getAddress, isMale, getAge, setAge, setMale, setAddress]
[public, public, public, public, public, public, public, public, public, public, public]

getReturnedType()

This method lets us get the return type of the method:


static void getReturnedType(Method[] methods) {
    Arrays.stream(methods)
            .map(Method::getReturnType)
            .forEach(System.out::println);
}
class java.lang.String
boolean
class java.lang.String
int
void
class java.lang.String
boolean
int
void
void
void

getGenericReturnType()

Let's give our Person class a method that returns the type wrapped in a parameterized type, and try to get its return value:


public List<String> someMethod() {
    // Very useful and important method
    return null;
}

And we'll update our main method:


static void getGenericReturnType(Method[] methods) {
    Arrays.stream(methods)
            .map(Method::getGenericReturnType)
            .forEach(System.out::println);
}

Result of our method:

class java.lang.String
boolean
class java.lang.String
int
void
class java.lang.String
boolean
int
void
void
void
java.util.List<java.lang.String>

getParameterTypes() and getGenericParameterTypes() methods

We continue to modify our Person class, adding two more methods:


public List<String> someMethod(List<String> list, String s) {
    // Very useful and important method
    return null;
}

The first one will let us get the parameters of our methods, and the second one will give us parameterized types as well.


static void getParameterTypes(Method[] methods) {
    Class<?>[] types = method.getParameterTypes();
        for (Class<?> type : types) {
            System.out.println(type);
        }
}

static void getGenericParameterTypes(Method[] methods) {
   Type[] types = method.getGenericParameterTypes();
        for (Type type : types) {
            System.out.println(type);
        }
}

We will access only one of our methods. To access a method by a specific name, we call getMethod and pass in the name and parameters of the method we want:


public static void main(String[] args) throws NoSuchMethodException {
    Method currentMethod = Person.class.getMethod("someMethod", List.class, String.class);

    getParameterTypes(currentMethod);
    System.out.println();
    getGenericParameterTypes(currentMethod);
}

Running our code, we will see how the methods differ and what they return:

interface java.util.List
class java.lang.String

java.util.List<java.lang.String>
class java.lang.String

getExceptionTypes() and getGenericExceptionTypes() methods

We can use these methods to get an array of exceptions that our method can throw, as well as exceptions with parameterized types (if any). Let's use a new example that has a hidden static class:


private static class Processor {
    private void init() {}

    private void process() throws IOException {}
}

And we'll call methods on our Processor class:


public static void main(String... args) throws NoSuchMethodException {
    Method method = Processor.class.getDeclaredMethod("process");
    Type[] type = method.getExceptionTypes();
    System.out.println(Arrays.toString(type));
}

Now we can see our exception:

[class java.io.IOException]

Now let's parameterize the type. We'll modify our main class:


private static class Processor<E extends IOException> {

    private void process() throws E {
    }
}

And the code of the main method:


public static void main(String... args) throws NoSuchMethodException {
    Method m = Processor.class.getDeclaredMethod("process");
    Type[] t = m.getGenericExceptionTypes();
    System.out.println(Arrays.toString(t));

    for (Type type : t) {
        if (type instanceof TypeVariable) {
            for (Type type1 : ((TypeVariable) type).getBounds()) {
                System.out.println(type1);
            }
        }
    }
}

Inside this method, we got a TypeVariables object, which is a generic parent interface for type variables. And inside that, we can now get the internal parameter, namely our nested exception:

[E]
class java.io.IOException

getAnnotations() and getDeclaredAnnotations() methods

Let's continue using this new class and add a couple of annotations to it. We'll create our own Annotation annotation:


@Retention(RetentionPolicy.RUNTIME)
@interface Annotation {

    public String key();
    public String value();
}

And apply it to our method:


@Annotation(key = "key", value = "value")
private void process() throws E{

}

And of course we'll add a method to display all of our annotations:


static void getMethodAnnotations(Class<?> clazz) {
    Method[] methods = clazz.getDeclaredMethods();
    for (Method method : methods) {
        System.out.println(method.getName());
        System.out.println(Arrays.toString(method.getAnnotations()));
        System.out.println();
    }
}

Implementation of our main method:


public static void main(String... args) {
    Class clazz = Processor.class;
    getMethodAnnotations(clazz);
}

The resulting screen output is:

process
[@com.company.Main&Annotation(key="key", value="value")]

This is how we can get the annotations that have been applied to our methods, and the getAnnotations method lets us access the parent annotations of the class as well.

Today we got acquainted with how reflection can work with methods and fields, and what data we can get with it!