Reflection API в Java. Класс Field. Часть 2
Данная статья:
- написана командой Vertex Academy. Надеемся, что она Вам будет полезна. Приятного прочтения!
- это одна из статей из нашего "Самоучителя по Java"
- Данная статья предполагает, что Вы уже хорошо знаете ООП.
Эта статья является ответвлением от статьи Reflection API в Java, Часть 1, где мы узнали как получить Field из класса Class.
В этой статье вы узнаете как работать с классом Field.
Класс Field
Класс Field предоставляет возможность:
- получить значение поля, его тип, имя а так же модификаторы поля
- получить список аннотаций, класс, в котором объявлено поле и другую информацию
- установить новое значение в поле, даже если оно объявлено как private
Для начала, создадим класс Car
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
package com.vertex.reflection; class Car { private int horsepower; public final String serialNumber; public Car() { serialNumber = ""; } public Car(int horsepower, String serialNumber) { this.horsepower = horsepower; this.serialNumber = serialNumber; } } |
Получение значения переменной
Для того, чтобы получить значение из класса Field существуют методы getByte(), getShort(), getInt(), getLong(), getFloat(), getDouble(), getChar(), getBoolean() и get(). Как вы уже догадались, первые 8 методов существуют для получения примитивов, а последний для получения объектов.
Пример 1 Получение значения public переменной
1 2 3 4 5 6 |
Car car = new Car(500, "1233"); Class<? extends Car> carClass = car.getClass(); Field serialNumberField = carClass.getDeclaredField("serialNumber"); String serialNumberValue = (String) serialNumberField.get(car); //указываем из какого объекта мы хотим получить значение System.out.println(serialNumberValue); //output: 1233 |
Как видите, метод get() принимает обьект car, у которого мы и хотим спросить значение поля.
При вызове метода get() у serialNumber нам необходимо использовать кастование (от англ - cast) т.к. метод get() возвращает тип Object, но если мы знаем тип, то мы можем воспользоваться этим и преобразовать его к нужному нам.
Пример 2 Получение значения private переменной
1 2 3 4 5 6 |
Car car = new Car(500, "1233"); Class<? extends Car> carClass = car.getClass(); Field horsepowerField = carClass.getDeclaredField("horsepower"); int horsepowerValue = horsepowerField.getInt(car); System.out.println(horsepowerValue); |
Запускаем код ииии.... видим вот такое исключение.
1 2 |
java.lang.IllegalAccessException: class com.vertex.reflection.Main cannot access a member of class com.vertex.reflection.Car with modifiers "private" |
А всё потому, что нельзя просто так взять и получить значение приватной переменной. Для этого, перед вызовом метода getInt(), необходимо вызвать метод setAccessible(true).
1 2 3 4 5 6 7 |
Car car = new Car(500, "1233"); Class<? extends Car> carClass = car.getClass(); Field horsepowerField = carClass.getDeclaredField("horsepower"); horsepowerField.setAccessible(true); int horsepowerValue = horsepowerField.getInt(car); System.out.println(horsepowerValue); //output: 500 |
И все работает как часы.
Пример 3 Получение имени, типа и модификаторов переменной
1 2 3 4 5 6 7 8 9 10 11 12 |
Car car = new Car(500, "1233"); Class<? extends Car> carClass = car.getClass(); Field horsepowerField = carClass.getDeclaredField("horsepower"); String name = horsepowerField.getName(); System.out.println(name); //output: horsepower Class<?> type = horsepowerField.getType(); System.out.println(type); //output: int int modifiers = horsepowerField.getModifiers(); System.out.println(modifiers); //output: 2 |
Если с первыми двумя методами все понятно, то с третим не все так гладко. Все дело в том, что метод getModifiers() возвращает все модификаторы метода в битовом представлении. Для работы с ним необходимо воспользоваться методами класса Modifier
1 2 3 4 5 6 7 8 |
Car car = new Car(500, "1233"); Class<? extends Car> carClass = car.getClass(); Field horsepowerField = carClass.getDeclaredField("horsepower"); int modifiers = horsepowerField.getModifiers(); System.out.println(Modifier.isPrivate(modifiers)); // output: true System.out.println(Modifier.isFinal(modifiers)); // output: false |
Пример 4 Получение аннотаций переменной
Для получения аннотаций переменной существуют методы : getAnnotations() и getDeclaredAnnotations(), getAnnotationsByType() и getDeclaredAnnotationsByType(), getAnnotation() и getDeclaredAnnotation()
Но, пары методов getAnnotations() и getDeclaredAnnotations(), getAnnotationsByType() и getDeclaredAnnotationsByType(), getAnnotation() и getDeclaredAnnotation() делают одно и то же. Как пример, javadoc для getDeclaredAnnotation():
1 2 3 |
// Only annotations on classes are inherited, for all other // objects getDeclaredAnnotation is the same as // getAnnotation. |
У класса Field присутствуют эти методы т.к. он реализует общий со всеми типами интерфейс AnnotatedElement.
НО, предназначались эти методы для классов, т.к. только аннотации на классах наследуются. Чтобы влезть в существующую архитектуру JDK, разработчики решили просто реализовать один метод через другой, что и можно увидеть в классе AccessibleObject.
1 2 3 4 5 6 |
/** * @since 1.5 */ public Annotation[] getAnnotations() { return getDeclaredAnnotations(); } |
Поэтому мы будет использовать только три из них
Для примера нам нужно будет добавить аннотацию над полем horsepower
1 2 |
@Description("the power of an engine") private int horsepower; |
А теперь посмотрим на их использование
1 2 3 4 5 6 7 8 9 10 11 12 |
Car car = new Car(500, "1233"); Class<? extends Car> carClass = car.getClass(); Field horsepowerField = carClass.getDeclaredField("horsepower"); Annotation[] annotations = horsepowerField.getAnnotations(); System.out.println(Arrays.toString(annotations)); //output: [@jdk.jfr.Description(value="the power of an engine")] Description descriptionAnnotation = horsepower.getAnnotation(Description.class); System.out.println(descriptionAnnotation); //output: [@jdk.jfr.Description(value="the power of an engine")] Annotation[] annotationsByType = horsepower.getAnnotationsByType(Description.class); System.out.println(Arrays.toString(annotationsByType)); //output: [@jdk.jfr.Description(value="the power of an engine")] |
- getAnnotations() возвращает массив аннотаций метода
- getAnnotation() возвращает аннотацию по типу
- getAnnotationsByType() возвращает массив аннотаций по типу. Метод был добавлен в Java 8 вместе с @Repeatable аннотациями
Пример 5 Получение дополнительных данных
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
Car car = new Car(500, "1233"); Class<? extends Car> carClass = car.getClass(); Field horsepowerField = carClass.getDeclaredField("horsepower"); Class<?> declaringClass = horsepowerField.getDeclaringClass(); // возвращает класс, в котором объявлено поле System.out.println(declaringClass); //output: class com.vertex.reflection.Car boolean enumConstant = horsepowerField.isEnumConstant(); //возвращает true, если поле является значением перечисления System.out.println(enumConstant); //output: false boolean synthetic = horsepowerField.isSynthetic(); // возвращает true, если метод синтетический (генерируется JVM) System.out.println(synthetic); //output: false boolean canAccess = horsepowerField.canAccess(car); // возвращает true, если к полю есть доступ (к примеру, оно не приватное) System.out.println(canAccess); //output: false boolean isPresent = horsepowerField.isAnnotationPresent(Description.class);// возвращает true, если такая аннотация присутствует на поле System.out.println(isPresent); //output: false System.out.println(horsepowerField.isAnnotationPresent(NamedArg.class)); //output: true |
Изменение значения переменных
И наконец мы узнаем как изменить значение переменной. Для этого у нас есть методы setByte(), setShort(), setInt(), setLong(), setFloat(), setDouble(), setChar(), setBoolean() и set() который принимает ссылочные типы данных.
Пример 6 Изменение значения public переменной
1 2 3 4 5 6 7 |
Car car = new Car(500, "1233"); Class<? extends Car> carClass = car.getClass(); Field serialNumberField = carClass.getField("serialNumber"); System.out.println("Before change:" + serialNumberField.get(car)); serialNumberField.set(car, "37U1"); System.out.println("After change:" + serialNumberField.get(car)); |
И нас ожидает такой вывод в консоль
1 2 3 |
Before change:1233 Exception in thread "main" java.lang.IllegalAccessException: Can not set final java.lang.String field com.vertex.reflection.Car.serialNumber to java.lang.String |
Если вы позабыли, то переменная serialNumber у нас final. А у final переменной нельзя просто так взять и изменить значение. Но если уж очень сильно хочется, то setAccessible(true) вам в помощь
1 2 3 4 5 6 7 8 |
Car car = new Car(500, "1233"); Class<? extends Car> carClass = car.getClass(); Field serialNumberField = carClass.getField("serialNumber"); System.out.println("Before change:" + serialNumberField.get(car)); serialNumberField.setAccessible(true); serialNumberField.set(car, "37U1"); System.out.println("After change:" + serialNumberField.get(car)); |
После чего можно наблюдать следующий вывод в консоль
1 2 |
Before change:1233 After change:37U1 |
Пример 7 Изменение значения private переменной
Как и в случае с получением значение private переменной или изменением final переменной, перед использованием одного из методов set необходимо вызвать метод setAccessible(true)
1 2 3 4 5 6 7 8 |
Car car = new Car(500, "1233"); Class<? extends Car> carClass = car.getClass(); Field horsepowerField = carClass.getDeclaredField("horsepower"); horsepowerField.setAccessible(true); System.out.println("Before change:" + horsepowerField.getInt(car)); horsepowerField.setInt(car, 751); System.out.println("After change:" + horsepowerField.getInt(car)); |
Вывод будет таковым
1 2 |
Before change:500 After change:751 |
На этом урок заканчивается
В этом уроке мы узнали:
- как получить значение поля, его тип, имя, а так же модификаторы поля
- как получить список аннотаций, класс, в котором объявлено поле и другую информацию
- как установить новое значение в поле, даже если оно объявлено как private или final
Спасибо, что были с нами! 🙂
Надеемся, что наша статья была Вам полезна. Можно записаться к нам на курсы по Java на сайте.