За Howво е Reflection API?

Механизмът за отразяване на Java позволява на разработчика да прави промени и да получава информация за класове, интерфейси, полета и методи по време на изпълнение, без да знае техните имена.

Reflection API също ви позволява да създавате нови обекти, да извиквате методи и да получавате or задавате стойности на полета.

Нека направим списък на всичко, което можете да направите с помощта на отражение:

  • Идентифицирайте/определете класа на обект
  • Получете информация за модификатори на класове, полета, методи, константи, конструктори и суперкласове
  • Разберете кои методи принадлежат към внедрен(и) интерфейс(и)
  • Създайте екземпляр на клас, чието име на клас не е известно, докато програмата не бъде изпълнена
  • Вземете и задайте стойността на поле на екземпляр по име
  • Извикайте метод на екземпляр по име

Почти всички съвременни Java технологии използват отражение. Той е в основата на повечето днешни Java / Java EE рамки и библиотеки, например:

  • Spring frameworks за изграждане на уеб applications
  • рамката за тестване на JUnit

Ако никога преди не сте се сблъсквали с тези механизми, вероятно се питате защо е необходимо всичко това. Отговорът е доста прост, но и много неясен: отражението драстично увеличава гъвкавостта и способността да персонализираме нашето приложение и code.

Но винаги има плюсове и минуси. Така че нека споменем няколко недостатъка:

  • Нарушения на сигурността на приложението. Отражението ни позволява достъп до code, който не трябва (нарушаване на капсулирането).
  • Ограничения за сигурност. Отражението изисква разрешения по време на изпълнение, които не са достъпни за системи, работещи с мениджър за защита.
  • Ниска производителност. Reflection в Java определя типовете динамично чрез сканиране на класовия път , за да намери класа за зареждане. Това намалява производителността на програмата.
  • Трудна за поддръжка. Кодът, който използва отражение, е труден за четене и отстраняване на грешки. Той е по-малко гъвкав и по-труден за поддръжка.

Работа с класове с помощта на Reflection API

Всички операции за отразяване започват с обект java.lang.Class . За всеки тип обект се създава неизменен екземпляр на java.lang.Class . Той предоставя методи за получаване на свойства на обекти, създаване на нови обекти и извикване на методи.

Нека да разгледаме списъка с основни методи за работа с java.lang.Class :

Метод Действие
Низ getName(); Връща името на класа
int getModifiers(); Връща модификатори за достъп
Пакет getPackage(); Връща информация за пакет
Клас getSuperclass(); Връща информация за родителски клас
Class[] getInterfaces(); Връща масив от интерфейси
Конструктор[] getConstructors(); Връща информация за конструктори на класове
Полета[] getFields(); Връща полетата на клас
Поле getField(String fieldName); Връща конкретно поле от клас по име
Метод[] getMethods(); Връща масив от методи

Това са най-важните методи за получаване на данни за класове, интерфейси, полета и методи. Има и методи, които ви позволяват да получавате or задавате стойности на полета и да осъществявате достъп до частни полета. Ще ги разгледаме малко по-късно.

Точно сега ще говорим за получаване на самия java.lang.Class . Имаме три начина да направим това.

1. Използване на Class.forName

В работещо приложение трябва да използвате метода forName(String className) , за да получите клас.

Този code демонстрира How можем да създаваме класове, използвайки отражение. Нека създадем клас Person , с който можем да работим:

package com.company;

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

    public int getAge() {
        return age;
    }

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

    public String getName() {
        return name;
    }

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

И втората част от нашия пример е codeът, който използва отражение:

public class TestReflection {
    public static void main(String[] args) {
        try {
            Class<?> aClass = Class.forName("com.company.Person");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

Този подход е възможен, ако е известно пълното име на класа. След това можете да получите съответния клас, като използвате статичния метод Class.forName() . Този метод не може да се използва за примитивни типове.

2. Използване на .class

Ако даден тип е наличен, но няма екземпляр от него, тогава можете да получите класа, като добавите .class към името на типа. Това е най-лесният начин да получите клас от примитивен тип.

Class aClass = Person.class;

3. Използване на .getClass()

Ако даден обект е наличен, тогава най-лесният начин да получите клас е да извикате object.getClass() .

Person person = new Person();
Class aClass = person.getClass();

Каква е разликата между последните два подхода?

Използвайте A.class, ако знаете кой клас обект ви интересува по време на codeиране. Ако няма наличен екземпляр, тогава трябва да използвате .class .

Получаване на методите на клас

Нека разгледаме методите, които връщат методите на нашия клас: getDeclaredMethods() и getMethods() .

getDeclaredMethods() връща масив, който съдържа Method обекти за всички декларирани методи на класа or интерфейса, представен от Class обекта, включително публични, частни, по подразбиране и защитени методи, но не и наследени методи.

getMethods() връща масив, съдържащ Method обекти за всички публични методи на класа or интерфейса, представени от Class обекта — тези, декларирани от класа or интерфейса, Howто и тези, наследени от суперкласове и суперинтерфейси.

Нека да разгледаме How работи всеки от тях.

Нека започнем с getDeclaredMethods() . За да ни помогнете отново да разберем разликата между двата метода, по-долу ще работим с абстрактния клас Numbers . Нека напишем статичен метод, който ще преобразува нашия масив Method в List<String> :

import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class TestReflection {
    public static void main(String[] args) {
        final Method[] declaredMethods = Number.class.getDeclaredMethods();
        List<String> actualMethodNames = getMethodNames(declaredMethods);
        actualMethodNames.forEach(System.out::println);
    }

    private static List<String> getMethodNames(Method[] methods) {
        return Arrays.stream(methods)
                .map(Method::getName)
                .collect(Collectors.toList());
    }
}

Ето резултата от изпълнението на този code:

byteValue
shortValue
intValue
longValue
float floatValue;
doubleValue

Това са методите, декларирани в класа Number . Какво връща getMethods() ? Нека променим два реда в примера:

final Method[] methods = Number.class.getMethods();
List<String> actualMethodNames = getMethodNames(methods);

Правейки това, ще видим следния набор от методи:

byteValue
shortValue
intValue
longValue
float floatValue;
doubleValue
изчакайте
изчакайте
изчакайте
равно
на String
hashCode
getClass
notify
notifyAll

Тъй като всички класове наследяват Object , нашият метод връща и публичните методи на класа Object .

Получаване на полетата на клас

Методите getFields и getDeclaredFields се използват за получаване на полетата на клас. Като пример, нека разгледаме класа LocalDateTime . Ще пренапишем нашия code:

import java.lang.reflect.Field;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class TestReflection {
    public static void main(String[] args) {
        final Field[] declaredFields = LocalDateTime.class.getDeclaredFields();
        List<String> actualFieldNames = getFieldNames(declaredFields);
        actualFieldNames.forEach(System.out::println);
    }

    private static List<String> getFieldNames(Field[] fields) {
        return Arrays.stream(fields)
                .map(Field::getName)
                .collect(Collectors.toList());
    }
}

В резултат на изпълнението на този code получаваме набора от полета, съдържащи се в класа LocalDateTime.

MIN
MAX
serialVersionUID
дата
час

По аналогия с предишното ни изследване на методите, нека видим Howво ще се случи, ако променим малко codeа:

final Field[] fields = LocalDateTime.class.getFields();
List<String> actualFieldNames = getFieldNames(fields);

Изход:

МИН.
МАКС

Сега нека разберем разликата между тези методи.

Методът getDeclaredFields връща масив от обекти Field за всички полета, декларирани от класа or интерфейса, представен от товаКласобект.

Методът getFields връща масив от обекти Field за всички публични полета на класа or интерфейса, представен отКласобект.

Сега нека погледнем вътре в LocalDateTime .

На класаМИНиМАКСполетата са публични, което означава, че ще бъдат видими чрез метода getFields . За разлика от това,дата,време,serialVersionUIDметодите имат частен модификатор, което означава, че няма да бъдат видими чрез метода getFields , но можем да ги получим с помощта на getDeclaredFields . Ето How можем да получим достъп до Field обекти за частни полета.

Описания на други методи

Сега е време да поговорим за някои методи на класа Class , а именно:

Метод Действие
getModifiers Получаване на модификаторите за нашия клас
getPackage Получаване на пакета, който съдържа нашия клас
getSuperclass Получаване на родителския клас
getInterfaces Получаване на масив от интерфейси, реализирани от клас
getName Получаване на пълно квалифицирано име на клас
getSimpleName Получаване на име на клас

getModifiers()

Модификаторите могат да бъдат достъпни чрез aКласобект.

Модификаторите са ключови думи като public , static , interface и т.н. Получаваме модификатори, използвайки метода getModifiers() :

Class<Person> personClass = Person.class;
int classModifiers = personClass.getModifiers();

Този code задава стойността на anвътрпроменлива, която е малко поле. Всеки модификатор на достъп може да бъде включен or изключен чрез задаване or изчистване на съответния бит. Можем да проверим модификаторите, като използваме методите в класа java.lang.reflect.Modifier :

import com.company.Person;
import java.lang.reflect.Modifier;

public class TestReflection {
    public static void main(String[] args) {
        Class<Person> personClass = Person.class;
        int classModifiers = personClass.getModifiers();

        boolean isPublic = Modifier.isPublic(classModifiers);
        boolean isStatic = Modifier.isStatic(classModifiers);
        boolean isFinal = Modifier.isFinal(classModifiers);
        boolean isAbstract = Modifier.isAbstract(classModifiers);
        boolean isInterface = Modifier.isInterface(classModifiers);

        System.out.printf("Class modifiers: %d%n", classModifiers);
        System.out.printf("Is public: %b%n", isPublic);
        System.out.printf("Is static: %b%n", isStatic);
        System.out.printf("Is final: %b%n", isFinal);
        System.out.printf("Is abstract: %b%n", isAbstract);
        System.out.printf("Is interface: %b%n", isInterface);
    }
}

Припомнете си How изглежда декларацията на нашето лице :

public class Person {}

Получаваме следния изход:

Модификатори на класа: 1
е публичен: вярно
е статичен: невярно
е окончателен: невярно
е абстрактен: невярно
е интерфейс: невярно

Ако направим нашия клас абстрактен, тогава имаме:

public abstract class Person {}

и този изход:

Модификатори на класа: 1025
е публичен: вярно
е статичен: невярно
е окончателен: невярно
е абстрактен: вярно
е интерфейс: невярно

Променихме модификатора за достъп, което означава, че променихме и данните, върнати чрез статичните методи на класа Modifier .

getPackage()

Познавайки само клас, можем да получим информация за неговия пакет:

Class<Person> personClass = Person.class;
final Package aPackage = personClass.getPackage();
System.out.println(aPackage.getName());

getSuperclass()

Ако имаме обект Class, тогава можем да получим достъп до неговия родителски клас:

public static void main(String[] args) {
    Class<Person> personClass = Person.class;
    final Class<? super Person> superclass = personClass.getSuperclass();
    System.out.println(superclass);
}

Получаваме добре познатия клас Object :

class java.lang.Object

Но ако нашият клас има друг родителски клас, тогава ще го видим instead of него:

package com.company;

class Human {
    // Some info
}

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

    // Some info
}

Тук получаваме нашия родителски клас:

class com.company.Human

getInterfaces()

Ето How можем да получим списъка с интерфейси, реализирани от класа:

public static void main(String[] args) {
    Class<Person> personClass = Person.class;
    final Class<?>[] interfaces = personClass.getInterfaces();
    System.out.println(Arrays.toString(interfaces));
}

И нека не забравяме да променим нашия клас Person :

public class Person implements Serializable {}

Изход:

[интерфейс java.io.Serializable]

Един клас може да реализира много интерфейси. Ето защо получаваме масив отКласобекти. В Java Reflection API интерфейсите също са представени отКласобекти.

Моля, обърнете внимание: Методът връща само интерфейсите, реализирани от посочения клас, а не неговия родителски клас. За да получите пълен списък на интерфейсите, реализирани от класа, трябва да се обърнете Howто към текущия клас, така и към всички негови предшественици нагоре по веригата на наследяване.

getName() & getSimpleName() & getCanonicalName()

Нека напишем пример, включващ примитивен, вложен клас, анонимен клас и клас String :

public class TestReflection {
    public static void main(String[] args) {
        printNamesForClass(int.class, "int class (primitive)");
        printNamesForClass(String.class, "String.class (ordinary class)");
        printNamesForClass(java.util.HashMap.SimpleEntry.class,
                "java.util.HashMap.SimpleEntry.class (nested class)");
        printNamesForClass(new java.io.Serializable() {
                }.getClass(),
                "new java.io.Serializable(){}.getClass() (anonymous inner class)");
    }

    private static void printNamesForClass(final Class<?> clazz, final String label) {
        System.out.printf("%s:%n", label);
        System.out.printf("\tgetName()):\t%s%n", clazz.getName());
        System.out.printf("\tgetCanonicalName()):\t%s%n", clazz.getCanonicalName());
        System.out.printf("\tgetSimpleName()):\t%s%n", clazz.getSimpleName());
        System.out.printf("\tgetTypeName():\t%s%n%n", clazz.getTypeName());
    }
}

Резултат от нашата програма:

int клас (примитивен):
getName()): int
getCanonicalName()): int
getSimpleName()): int
getTypeName(): int

String.class (обикновен клас):
getName()): java.lang.String
getCanonicalName() ): java.lang.String
getSimpleName()): String
getTypeName(): java.lang.String

java.util.HashMap.SimpleEntry.class (вложен клас):
getName()): java.util.AbstractMap$SimpleEntry
getCanonicalName( )): java.util.AbstractMap.SimpleEntry
getSimpleName()): SimpleEntry
getTypeName(): java.util.AbstractMap$SimpleEntry

нов java.io.Serializable(){}.getClass() (анонимен вътрешен клас):
getName() ): TestReflection$1
getCanonicalName()): нула
getSimpleName()):
getTypeName(): TestReflection$1

Сега нека анализираме изхода на нашата програма:

  • getName() връща името на обекта.

  • getCanonicalName() връща каноничното име на базовия клас, Howто е дефинирано от Java Language Specification. Връща null, ако базовият клас няма канонично име (т.е. ако е локален or анонимен клас or масив, чийто тип елемент няма канонично име).

  • getSimpleName() връща простото име на базовия клас, Howто е посочено в изходния code. Връща празен низ, ако базовият клас е анонимен.

  • getTypeName() връща информативен низ за името на този тип.