Клас 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();

За допомогою нього ми отримаємо всі методи в нашому класі.

Методи 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()

За допомогою цього методу ми маємо змогу отримати повертаємий тип методу:

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

За допомогою цих методів можна отримати масив винятків, які може викинути наш метод, а також винятків, огорнених у дженерики (якщо вони є). Візьмемо новий приклад із прихованим статичним класом:

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 ми отримуємо доступ і до батьківських анотацій класу.

Сьогодні ми з тобою познайомилися з тим, як працюють методи й поля з рефлексією та які дані ми можемо отримати за допомогою неї!