Reflection API в Java - Часть 1
Данная статья:
- написана командой Vertex Academy. Надеемся, что она Вам будет полезна. Приятного прочтения!
- это одна из статей из нашего "Самоучителя по Java"
- Данная статья предполагает, что Вы уже хорошо знаете ООП.
В этой статье мы узнаем, что такое Рефлексия (Reflection) в Java, зачем она нам нужна, каковы её минусы, а также научимся базовой работе с ней.
Для начала нам необходимо будет разобрать немного теории.
Что такое Рефлексия?
Рефлексия - это API, который позволяет:
- получать информацию о переменных, методах внутри класса, о самом классе, его конструкторах, реализованных интерфейсах и т.д.;
- получать новый экземпляр класса;
- получать доступ ко всем переменным и методам, в том числе приватным;
- преобразовывать классы одного типа в другой (cast);
- делать все это во время исполнения программы (динамически, в Runtime).
Минусы Рефлексии
Как и у всего в этом мире, у Рефлексии есть свои недостатки:
- Худшая производительность в сравнении с классической работой с классами, методами и переменными;
- Ограничения безопасности. Если мы захотим использовать рефлексию на классе, который защищен с помощью специального класса SecurityManager, то ничего у не выйдет т.к. этот класс будет выбрасывать исключения каждый раз, как мы попытаемся получить доступ к закрытым членам класса. Такая защита может применяться, например, в Апплетах (Applets);
- Получение доступа к внутренностям класса, что нарушает принцип инкапсуляции. Фактически, мы получаем доступ туда, куда обычному человеку лезть не желательно. Это как с розеткой, ребёнку лучше к ней не лезть, тогда как опытный электрик запросто с ней поладит.
Что такое Класс класса, у кого он есть?
В Java есть специальный класс по имени Class, да-да, именно Class. Поэтому его и называют классом класса. С помощью него осуществляется работа с рефлексией, он и является входной точкой в мир рефлексии.
Class есть у :
- классов, интерфейсов, перечислений;
- примитивов и обёрток над ними;
- массивов;
- void. Да, ключевое слово void также имеет Class.
В общем, Class есть у всех объектов в Java.
А теперь перейдем к практике, для этого нам понадобится класс Car
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
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; } public int getHorsepower() { return horsepower; } void setHorsepower(int horsepower) { this.horsepower = horsepower; } protected void printSerialNumber() { System.out.println(serialNumber); } } |
Как получить Класс класса?
Способ 1 - Сlass.forName(“имя.пакета.ИмяКласса”)
1 2 3 4 5 |
try { Class<?> carClass = Class.forName("com.vertex.reflection.Car"); } catch (ClassNotFoundException e) { e.printStackTrace(); } |
Вызов метода forName() необходимо обернуть в блок try-catch т.к. метод может бросить ClassNotFoundException, в случае если он не найдет класс с таким именем.
Способ 2 - метод getClass() у экземпляра класса
1 2 |
Car car = new Car(); Class<? extends Car> carClass = car.getClass(); |
В этом случае оборачивать метод getClass() в блок try-catch нет необходимости т.к. мы вызываем этот метод у существующего класса, который видит компилятор. Но, к сожалению, компилятор не может знать тип переменной до конца, поэтому мы и имеем "? extends Car", как дженерик тип.
Способ 3 - ИмяКласса.class
1 |
Class<Car> carClass = Car.class; |
Здесь по той же причине не нужно использовать блок try-catch.
Если вы заметили, то чем больше мы знаем о классе, тем точнее будет тип класса при получении Класса класса.
В первом случае мы знаем только относительный путь к классу, это самый ненадёжный способ, поэтому и тип дженерика был Class<?>. Ведь тип будет известен только после того, как мы запустим программу, компилятор этого знать не может наперёд.
Во втором случае мы получили Класс класса прямиком из экземпляра класса. Такой способ надёжнее, но он является не самым оптимальным. В этом случае компилятор не знает, экземпляр этого класса перед нами или его наследник, поэтому он подставляет тип Class<? extends Car>.
В третьем случае мы прямо указываем из какого класса мы хотим получить Класс. Тут нет возможности ошибится в имени, нет возможности получить класс наследника класса Car, как это было возможно во втором случае. Компилятор точно знает, что это за тип класса. Этот способ является самым надёжным, поэтому и тип здесь Class<Car>.
Как получить информацию о переменных класса с помощью Рефлексии?
Получить информацию о переменных класса можно с помощью методов getDeclaredFields(), getDeclaredField() и getFields(), getField().
Пример 1 getDeclaredFields()
Метод возвращает все объявленные переменные в классе
1 2 3 4 5 |
Class<Car> carClass = Car.class; Field[] declaredFields = carClass.getDeclaredFields(); for (Field field :declaredFields) { System.out.println(field); } |
Вывод
1 2 |
private int com.vertex.reflection.Car.horsepower public java.lang.String com.vertex.reflection.Car.serialNumber |
Пример 2 getDeclaredField()
Метод возвращает переменную по её имени. Если переменной с таким именем нет, то метод выбросит checked NoSuchFieldException.
1 2 3 4 5 6 7 8 |
Class<Car> carClass = Car.class; try { Field horsepowerField = carClass.getDeclaredField("horsepower"); System.out.println(horsepowerField); Field blaBlaField = carClass.getDeclaredField("bla_bla"); } catch (NoSuchFieldException e) { e.printStackTrace(); } |
Вывод
1 2 |
private int com.vertex.reflection.Car.horsepower java.lang.NoSuchFieldException: bla_bla |
Пример 3 getFields()
В отличии от метода getDeclaredFields(), метод getFields() возвращает только public переменные
1 2 3 4 5 |
Class<Car> carClass = Car.class; Field[] fields = carClass.getFields(); for (Field field : fields) { System.out.println(field); } |
Вывод
1 |
public java.lang.String com.vertex.reflection.Car.serialNumber |
Пример 4 getField()
По аналогии с методом getFields(), метод getField() возвращает только public переменные. Даже если поле с таким именем есть, но оно не публичное, метод getField() бросит NoSuchFieldException
1 2 3 4 5 6 7 8 |
Class<Car> carClass = Car.class; try { Field serialNumberField = carClass.getField("serialNumber"); System.out.println(serialNumberField); Field horsepowerField = carClass.getField("horsepower"); } catch (NoSuchFieldException e) { e.printStackTrace(); } |
Вывод
1 2 |
public java.lang.String com.vertex.reflection.Car.serialNumber java.lang.NoSuchFieldException: horsepower |
Как получить информацию о методах в классе с помощью Рефлексии?
Пример 1 getDeclaredMethods()
Метод возвращает все объявленнные методы в классе
1 2 3 4 5 |
Class<Car> carClass = Car.class; Method[] declaredMethods = carClass.getDeclaredMethods(); for (Method method : declaredMethods) { System.out.println(method); } |
1 2 3 |
public int com.vertex.reflection.Car.getHorsepower() void com.vertex.reflection.Car.setHorsepower(int) protected void com.vertex.reflection.Car.printSerialNumber() |
Пример 2 getDeclaredMethod()
Метод getDeclaredMethod(String name, Class<?>... parameterTypes) принимает имя и var-args с типами параметров метода. Если такого метода в классе нет, мы получим checked NoSuchMethodException.
1 2 3 4 5 6 7 8 9 10 11 12 |
Class<Car> carClass = Car.class; try { Method printSerialNumberMethod = carClass.getDeclaredMethod("printSerialNumber"); System.out.println(printSerialNumber); Method setHorsepowerMethod = carClass.getDeclaredMethod("setHorsepower", int.class); System.out.println(setHorsepower); carClass.getDeclaredMethod("dodooooo"); } catch (NoSuchMethodException e) { e.printStackTrace(); } |
Вывод в консоль
1 2 3 |
protected void com.vertex.reflection.Car.printSerialNumber() void com.vertex.reflection.Car.setHorsepower(int) java.lang.NoSuchMethodException: com.vertex.reflection.Car.dodooooo() |
Пример 3 getMethods()
Метод возвращает все public методы класса и public методы его родительского класса/интерфейсов
1 2 3 4 5 |
Class<Car> carClass = Car.class; Method[] methods = carClass.getMethods(); for (Method method : methods) { System.out.println(method); } |
1 2 3 4 5 6 7 8 9 10 |
public int com.vertex.reflection.Car.getHorsepower() public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException public final void java.lang.Object.wait() throws java.lang.InterruptedException public boolean java.lang.Object.equals(java.lang.Object) public java.lang.String java.lang.Object.toString() public native int java.lang.Object.hashCode() public final native java.lang.Class java.lang.Object.getClass() public final native void java.lang.Object.notify() public final native void java.lang.Object.notifyAll() |
Пример 4 getMethod()
Как и getMethods(), метод getMethod() возвращает только публичные методы. Если такого метода нет или он не публичный, мы получим NoSuchMethodException.
1 2 3 4 5 6 7 8 9 |
Class<Car> carClass = Car.class; try { Method getHorsepowerMethod = carClass.getMethod("notifyAll"); System.out.println(getHorsepowerMethod); Method printSerialNumberMethod = carClass.getMethod("printSerialNumber"); } catch (NoSuchMethodException e) { e.printStackTrace(); } |
1 2 |
public final native void java.lang.Object.notifyAll() java.lang.NoSuchMethodException: ua.vertex.reflection.Car.printSerialNumber() |
Пример 5 getEnclosingMethod()
Если класс является локальным или анонимным, метод getEnclosingMethod() возвращает тот метод в котором этот класс был создан, иначе метод возвращает null.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
package com.vertex.reflection; class Main { public static void anonymousClassExample() { Car car = new Car() { @Override protected void printSerialNumber() { } }; Method enclosingMethod = car.getClass().getEnclosingMethod(); System.out.println(enclosingMethod); } } |
Вывод в консоль:
1 |
public static void com.vertex.reflection.Main.anonymousClassExample() |
На этом урок заканчивается
В этом уроке мы узнали:
- что такое рефлексия;
- каковы её минусы;
- что такое класс класса;
- как получить класс класса;
- как получить информацию о переменных класса;
- как получить информацию о методах класса.
Следующую статью "Reflection API в Java. Класс Field" читайте здесь.
Спасибо, что были с нами! 🙂
Надеемся, что наша статья была Вам полезна. Можно записаться к нам на курсы по Java на сайте.