Класс java.lang.reflect.Field

Класс Field предоставляет информацию и динамический доступ к одному полю класса или интерфейса. Field также разрешает расширение преобразований во время операции get или set access, но выдает исключение IllegalArgumentException, если происходит сужение преобразования.

Чтобы получить класс Filed, мы напишем класс, с которым будем работать, а так же обработчик для этого:


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 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);
    }
}

Таким образом мы получаем список полей нашего класса, с которыми будем дальше работать. Результат выполнения выглядит так:

[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]

Теперь давай разбираться, что мы можем сделать с эти набором данных. Поговорим о методах класса Field:

Метод Описание
getType() Возвращает объект класса, который определяет объявленный тип поля, представленного этим объектом Field.
getAnnotatedType() Возвращает объект AnnotatedType, который представляет использование типа для указания объявленного типа поля, представленного этим полем.
getGenericType() Возвращает объект Type, который представляет объявленный тип поля, представленного этим объектом Field.
getName() Возвращает имя поля, представленного этим объектом Field.
getModifiers() Возвращает модификаторы языка Java для поля, представленного этим объектом Field, в виде целого числа.
getAnnotations() Возвращает аннотации этого поля. Если аннотаций нет — пустой массив.

Методы getType(), getName(), getModifiers()

С помощью метода getType() мы можем получить тип нашего поля. Напишем метод:


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

И получим такой результат:

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

И давай сразу добавим в наш класс метод для получения имени поля. Так будет проще ориентироваться по полям нашего класса.


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

Получим результат, уже более понятный для пользователя:

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

Отлично, давай еще модифицировать наш метод! Добавим сюда модификаторы доступа:


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())));
}

И давай разберем, что возвращает e.getModifiers(). Этот метод возвращает число int, внутри которого мы можем определить модификаторы доступа нашего поля. Внутри класса Modifier лежат статические переменные, которые отвечают за определенный модификатор поля.

Обернем наше возвращаемое значение в Мodifier.toString() — и сразу получим значение в текстовом виде:

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

И вот так это выглядит без Мodifier.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(), getGenericType()

Давай модифицируем класс Person для работы с текущими методами. Мы напишем свою аннотацию, которую добавим к нашим полям, и добавим еще немного полей.

Создадим две аннотации. В одну будем передавать имя переменной на русском, а вторую будем использовать для элементов:


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

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

И изменим наш основной класс и класс Person:


public class Person {
    @Name(name = "Имя")
    private String name;

    @Name(name = "Никнэймы пользователя")
    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())));
}

Самое время посмотреть на результат наших методов и разобрать еще раз их назначение:

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="\u041d\u0438\u043a\u0438 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f")]
[@Name(name="\u0418\u043c\u044f")]
[]
  • getAnnotatedType возвращает аннотацию для данного поля, если такая есть. У нас есть аннотация для поля и мы отлично ее видим.

  • getGenericType позволяет корректно отображать дженерализованные параметры.

  • getAnnotations возвращает аннотации, которые стоят над нашим объектом.

Таким образом мы можем легко получить все данные о каждом поле в нашем классе, о его модификаторах доступа, аннотациях и типах данных.

Класс java.lang.reflect.Method

Супер, мы поговорили о полях нашего класса, пора поговорить о методах.

Чтобы получить объект класса Method, мы вызовем getMethod и передадим туда имя нашего метода. Это базовый способ для получения класса Method:


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

Будем продолжать работать с нашим классом. Добавим геттеры и сеттеры, хэш-код, equals и toString:


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);
    }
}

И давай подготовим набор методов, которые будем смотреть у класса Method. Вот список основных методов:

Метод Описание
getName() Возвращает имя метода.
getModifiers() Возвращает модификатор доступа этого метода.
getReturnType() Возвращает возвращаемый тип метода.
getGenericReturnType() Возвращает возвращаемый тип метода с учетом дженерализированных методов.
getParameterTypes() Возвращает массив параметров метода.
getGenericParameterTypes() Возвращает массив параметров метода с учетом дженерализированных методов.
getExceptionTypes() Возвращает исключения, которые может выбросить метод.
getGenericExceptionTypes() Возвращает исключения, которые может выбросить метод, с учетом дженерализированных параметров.
getAnnotations() Возвращает аннотации для метода, включая родительские аннотации.
getDeclaredAnnotations() Возвращает аннотации для метода, игнорируя родительские аннотации.

Чтобы получить массив методов, которые вернет нам наш класс, мы можем вызвать такой метод:


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

C его помощью мы получим все методы в нашем классе.

Методы getName() и getModifiers()

Чтобы получить название всех методов, мы можем воспользоваться getName:


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

И чтобы получить модификаторы, напишем метод с использованием getModifiers:


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

Наш метод main:


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

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

Наш результат:

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

Все наши модификаторы имеют доступ public, поэтому последний метод возвращает нам единицы. Если мы модифицируем наш код, то увидим наши модификаторы:


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()

C помощью этого метода мы можем получить возвращаемый тип метода:


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()

Добавим в наш класс Person метод, который возвращает обернутый в дженерик тип, и попробуем получить его возвращаемое значение:


public List<String> someMethod() {
    //очень полезный и важный метод
    return null;
}

И модифицируем наш основной метод:


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

Результат нашего метода:

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() и getGenericParameterTypes()

Продолжаем модифицировать наш метод класса Person и добавим еще два метода:


public List<String> someMethod(List<String> list, String s) {
    //очень полезный и важный метод
    return null;
}

Первый позволит нам получить параметры наших методов, а второй выдаст еще и дженерализированые параметры.


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);
        }
}

Будем обращаться только к одному нашему методу. Чтобы обратится к методу по определенному названию, вызовем getMethod и передадим туда название и параметры нужного нам метода:


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);
}

И по результатам нашего кода увидим, чем отличаются методы и что они возвращают:

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

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

Методы getExceptionTypes() и getGenericExceptionTypes()

C помощью этих методов можно получить массив исключений, которые может выбросить наш метод, а еще исключения, обернутые в дженерики (если они есть). Возьмем новый пример со скрытым статическим классом:


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

    private void process() throws IOException {}
}

И будем вызывать методы у нашего класса Process:


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

В результате отлично видно наше исключение:

[class java.io.IOException]

Теперь обернем это все в дженерик. Модифицируем наш основной класс:


private static class Processor<E extends IOException> {

    private void process() throws E {
    }
}

И код класса Main:


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);
            }
        }
    }
}

Внутри этого метода мы получили TypeVariables — это общий супер-интерфейс для переменных типа. А внутри его уже мы можем получить внутренний параметр, а именно наше вложенной исключение:

[E]
class java.io.IOException

Методы getAnnotations() и getDeclaredAnnotations()

Продолжим работать с нашим новым классом и добавим в него парочку аннотаций. Создаем собственную аннотацию Annotation:


@Retention(RetentionPolicy.RUNTIME)
@interface Annotation {

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

И добавляем ее к нашему методу:


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

}

И, конечно, метод, чтобы отобразить все наши аннотации:


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();
    }
}

Реализация нашего Main класса:


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

Получим такой результат на экране:

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

Таким образом мы можем получить аннотации, которые принадлежат нашим методам, а с помощью метода getAnnotations мы получаем доступ также и к родительским аннотациям класса.

Сегодня мы с тобой познакомились с тем, как работают методы и поля с рефлексией и какие данные мы можем получить с ее помощью!