Ръководство за Java Reflection

1. Общ преглед

В тази статия ще изследваме отражението на Java, което ни позволява да инспектираме и / или да модифицираме атрибутите по време на изпълнение на класове, интерфейси, полета и методи. Това е особено полезно, когато не знаем имената им по време на компилация.

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

2. Настройка на проекта

За да използваме Java отражение, не е необходимо да включваме специални буркани , специална конфигурация или зависимости на Maven. JDK се доставя с група класове, които са включени в пакета java.lang.reflect специално за тази цел.

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

import java.lang.reflect.*;

и сме добре да тръгваме.

За да получим достъп до информация за класа, метода и полето на даден екземпляр, ние извикваме метода getClass , който връща представяне на класа по време на изпълнение на обекта. Върнатият обект на клас предоставя методи за достъп до информация за клас.

3. Прост пример

За да намокрим краката си, ще разгледаме един много основен пример, който проверява полетата на прост Java обект по време на изпълнение.

Нека създадем прост клас Person само с имена и възрастови полета и изобщо без методи. Ето го класът Person:

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

Сега ще използваме отражението на Java, за да открием имената на всички полета от този клас. За да оценим силата на отражението, ще конструираме обект Person и ще използваме Object като референтен тип:

@Test public void givenObject_whenGetsFieldNamesAtRuntime_thenCorrect() { Object person = new Person(); Field[] fields = person.getClass().getDeclaredFields(); List actualFieldNames = getFieldNames(fields); assertTrue(Arrays.asList("name", "age") .containsAll(actualFieldNames)); }

Този тест ни показва, че ние сме в състояние да получи набор от F ield предмети от нашето лице обект, дори ако позоваването на обекта е тип родител на този обект.

В горния пример се интересувахме само от имената на тези полета, но може да се направи много повече и ще видим допълнителни примери за това в следващите раздели.

Забележете как използваме помощен метод за извличане на действителните имена на полета, това е много основен код:

private static List getFieldNames(Field[] fields) { List fieldNames = new ArrayList(); for (Field field : fields) fieldNames.add(field.getName()); return fieldNames; }

4. Случаи на използване на Java Reflection

Преди да пристъпим към различните характеристики на отражението на Java, ще обсъдим някои от често срещаните приложения, които можем да намерим за него. Отражението на Java е изключително мощно и може да бъде много полезно по редица начини.

Например, в много случаи имаме конвенция за именуване на таблици с бази данни. Можем да изберем да добавим последователност, като предварително фиксираме имената на нашите таблици с tbl_ , така че таблица със студентски данни се нарича tbl_student_data .

В такива случаи можем да назовем обекта Java, съдържащ студентски данни, като Student или StudentData. След това, използвайки CRUD парадигмата, имаме по една входна точка за всяка операция, така че Създаването на операции да получава само параметър Object .

След това използваме отражение, за да извлечем името на обекта и имената на полетата. На този етап можем да картографираме тези данни в DB таблица и да присвоим стойностите на полето на обекта на съответните имена на полета на DB.

5. Проверка на Java класове

В този раздел ще разгледаме най-фундаменталния компонент в API за отразяване на Java. Обектите на Java клас, както споменахме по-рано, ни дават достъп до вътрешните детайли на всеки обект.

Ще разгледаме вътрешни подробности като име на клас на обект, модификатори, полета, методи, внедрени интерфейси и т.н.

5.1. Приготвям се

За да постигнем здраво схващане за API за отражение, както се прилага за класовете Java и да имаме примери с разнообразие, ще създадем абстрактен клас Animal, който реализира интерфейса Eating . Този интерфейс определя хранителното поведение на всеки конкретен обект от животни, който създаваме.

Първо, тук е интерфейсът за хранене :

public interface Eating { String eats(); }

и след това конкретното прилагане на интерфейса за хранене от животни :

public abstract class Animal implements Eating { public static String CATEGORY = "domestic"; private String name; protected abstract String getSound(); // constructor, standard getters and setters omitted }

Нека създадем и друг интерфейс, наречен Locomotion, който описва как се движи животно:

public interface Locomotion { String getLocomotion(); }

Сега ще създадем конкретен клас, наречен Goat, който разширява Animal и прилага Locomotion . Тъй като суперкласът прилага Eating , Goat ще трябва да внедри и методите на този интерфейс:

public class Goat extends Animal implements Locomotion { @Override protected String getSound() { return "bleat"; } @Override public String getLocomotion() { return "walks"; } @Override public String eats() { return "grass"; } // constructor omitted }

От този момент нататък ще използваме отражението на Java, за да проверим аспекти на Java обекти, които се появяват в класовете и интерфейсите по-горе.

5.2. Имена на класове

Нека започнем с получаване на името на обект от Класа :

@Test public void givenObject_whenGetsClassName_thenCorrect() { Object goat = new Goat("goat"); Class clazz = goat.getClass(); assertEquals("Goat", clazz.getSimpleName()); assertEquals("com.baeldung.reflection.Goat", clazz.getName()); assertEquals("com.baeldung.reflection.Goat", clazz.getCanonicalName()); }

Имайте предвид, че методът getSimpleName на Class връща основното име на обекта, както би се появило в неговата декларация. Тогава другите два метода връщат напълно квалифицираното име на класа, включително декларацията на пакета.

Нека също да видим как можем да създадем обект от класа Goat, ако знаем само неговото напълно квалифицирано име на клас:

@Test public void givenClassName_whenCreatesObject_thenCorrect(){ Class clazz = Class.forName("com.baeldung.reflection.Goat"); assertEquals("Goat", clazz.getSimpleName()); assertEquals("com.baeldung.reflection.Goat", clazz.getName()); assertEquals("com.baeldung.reflection.Goat", clazz.getCanonicalName()); }

Забележете, че името, което предаваме на метода static forName , трябва да включва информация за пакета. В противен случай ще получим ClassNotFoundException .

5.3. Модификатори на класа

Можем да определим модификаторите, използвани в клас, като извикаме метода getModifiers , който връща цяло число. Всеки модификатор е бит на флага, който е или зададен, или изчистен.

Класът java.lang.reflect.Modifier предлага статични методи, които анализират върнатото цяло число за наличие или отсъствие на определен модификатор.

Нека потвърдим модификаторите на някои от класовете, които дефинирахме по-горе:

@Test public void givenClass_whenRecognisesModifiers_thenCorrect() { Class goatClass = Class.forName("com.baeldung.reflection.Goat"); Class animalClass = Class.forName("com.baeldung.reflection.Animal"); int goatMods = goatClass.getModifiers(); int animalMods = animalClass.getModifiers(); assertTrue(Modifier.isPublic(goatMods)); assertTrue(Modifier.isAbstract(animalMods)); assertTrue(Modifier.isPublic(animalMods)); }

Ние сме в състояние да проверим модификатори от всеки клас, разположен в библиотечен буркан, който импортираме в нашия проект.

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

5.4. Информация за пакета

Използвайки отражение на Java, ние също така можем да получим информация за пакета на всеки клас или обект. Тези данни са групирани в клас Package, който се връща чрез извикване на метод getPackage за обекта на класа.

Нека пуснем тест за извличане на името на пакета:

@Test public void givenClass_whenGetsPackageInfo_thenCorrect() { Goat goat = new Goat("goat"); Class goatClass = goat.getClass(); Package pkg = goatClass.getPackage(); assertEquals("com.baeldung.reflection", pkg.getName()); }

5.5. Супер клас

Ние също така можем да получим суперклас на всеки клас Java, като използваме отражение на Java.

In many cases, especially while using library classes or Java's builtin classes, we may not know beforehand the superclass of an object we are using, this subsection will show how to obtain this information.

So let's go ahead and determine the superclass of Goat. Additionally, we also show that java.lang.String class is a subclass of java.lang.Object class:

@Test public void givenClass_whenGetsSuperClass_thenCorrect() { Goat goat = new Goat("goat"); String str = "any string"; Class goatClass = goat.getClass(); Class goatSuperClass = goatClass.getSuperclass(); assertEquals("Animal", goatSuperClass.getSimpleName()); assertEquals("Object", str.getClass().getSuperclass().getSimpleName()); }

5.6. Implemented Interfaces

Using Java reflection, we are also able to get the list of interfaces implemented by a given class.

Let's retrieve the class types of the interfaces implemented by the Goat class and the Animal abstract class:

@Test public void givenClass_whenGetsImplementedInterfaces_thenCorrect(){ Class goatClass = Class.forName("com.baeldung.reflection.Goat"); Class animalClass = Class.forName("com.baeldung.reflection.Animal"); Class[] goatInterfaces = goatClass.getInterfaces(); Class[] animalInterfaces = animalClass.getInterfaces(); assertEquals(1, goatInterfaces.length); assertEquals(1, animalInterfaces.length); assertEquals("Locomotion", goatInterfaces[0].getSimpleName()); assertEquals("Eating", animalInterfaces[0].getSimpleName()); }

Notice from the assertions that each class implements only a single interface. Inspecting the names of these interfaces, we find that Goat implements Locomotion and Animal implements Eating, just as it appears in our code.

You may have observed that Goat is a subclass of the abstract class Animal and implements the interface method eats(), then, Goat also implements the Eating interface.

It is therefore worth noting that only those interfaces that a class explicitly declares as implemented with the implements keyword appear in the returned array.

So even if a class implements interface methods because its superclass implements that interface, but the subclass does not directly declare that interface with the implements keyword, then that interface will not appear in the array of interfaces.

5.7. Constructors, Methods, and Fields

With Java reflection, we are able to inspect the constructors of any object's class as well as methods and fields.

We will, later on, be able to see deeper inspections on each of these components of a class but for now, it suffices to just get their names and compare them with what we expect.

Let's see how to get the constructor of the Goat class:

@Test public void givenClass_whenGetsConstructor_thenCorrect(){ Class goatClass = Class.forName("com.baeldung.reflection.Goat"); Constructor[] constructors = goatClass.getConstructors(); assertEquals(1, constructors.length); assertEquals("com.baeldung.reflection.Goat", constructors[0].getName()); }

We can also inspect the fields of the Animal class like so:

@Test public void givenClass_whenGetsFields_thenCorrect(){ Class animalClass = Class.forName("com.baeldung.reflection.Animal"); Field[] fields = animalClass.getDeclaredFields(); List actualFields = getFieldNames(fields); assertEquals(2, actualFields.size()); assertTrue(actualFields.containsAll(Arrays.asList("name", "CATEGORY"))); }

Just like we can inspect the methods of the Animal class:

@Test public void givenClass_whenGetsMethods_thenCorrect(){ Class animalClass = Class.forName("com.baeldung.reflection.Animal"); Method[] methods = animalClass.getDeclaredMethods(); List actualMethods = getMethodNames(methods); assertEquals(4, actualMethods.size()); assertTrue(actualMethods.containsAll(Arrays.asList("getName", "setName", "getSound"))); }

Just like getFieldNames, we have added a helper method to retrieve method names from an array of Method objects:

private static List getMethodNames(Method[] methods) { List methodNames = new ArrayList(); for (Method method : methods) methodNames.add(method.getName()); return methodNames; }

6. Inspecting Constructors

With Java reflection, we can inspect constructors of any class and even create class objects at runtime. This is made possible by the java.lang.reflect.Constructor class.

Earlier on, we only looked at how to get the array of Constructor objects, from which we were able to get the names of the constructors.

In this section, we will focus on how to retrieve specific constructors. In Java, as we know, no two constructors of a class share exactly the same method signature. So we will use this uniqueness to get one constructor from many.

To appreciate the features of this class, we will create a Bird subclass of Animal with three constructors. We will not implement Locomotion so that we can specify that behavior using a constructor argument, to add still more variety:

public class Bird extends Animal { private boolean walks; public Bird() { super("bird"); } public Bird(String name, boolean walks) { super(name); setWalks(walks); } public Bird(String name) { super(name); } public boolean walks() { return walks; } // standard setters and overridden methods }

Let's confirm by using reflection that this class has three constructors:

@Test public void givenClass_whenGetsAllConstructors_thenCorrect() { Class birdClass = Class.forName("com.baeldung.reflection.Bird"); Constructor[] constructors = birdClass.getConstructors(); assertEquals(3, constructors.length); }

Next, we will retrieve each constructor for the Bird class by passing the constructor's parameter class types in declared order:

@Test public void givenClass_whenGetsEachConstructorByParamTypes_thenCorrect(){ Class birdClass = Class.forName("com.baeldung.reflection.Bird"); Constructor cons1 = birdClass.getConstructor(); Constructor cons2 = birdClass.getConstructor(String.class); Constructor cons3 = birdClass.getConstructor(String.class, boolean.class); }

There is no need for assertion since when a constructor with given parameter types in the given order does not exist, we will get a NoSuchMethodException and the test will automatically fail.

In the last test, we will see how to instantiate objects at runtime while supplying their parameters:

@Test public void givenClass_whenInstantiatesObjectsAtRuntime_thenCorrect() { Class birdClass = Class.forName("com.baeldung.reflection.Bird"); Constructor cons1 = birdClass.getConstructor(); Constructor cons2 = birdClass.getConstructor(String.class); Constructor cons3 = birdClass.getConstructor(String.class, boolean.class); Bird bird1 = (Bird) cons1.newInstance(); Bird bird2 = (Bird) cons2.newInstance("Weaver bird"); Bird bird3 = (Bird) cons3.newInstance("dove", true); assertEquals("bird", bird1.getName()); assertEquals("Weaver bird", bird2.getName()); assertEquals("dove", bird3.getName()); assertFalse(bird1.walks()); assertTrue(bird3.walks()); }

We instantiate class objects by calling the newInstance method of Constructor class and passing the required parameters in declared order. We then cast the result to the required type.

It's also possible to call the default constructor using the Class.newInstance() method. However, this method has been deprecated since Java 9, and we shouldn't use it in modern Java projects.

For bird1, we use the default constructor which from our Bird code, automatically sets the name to bird and we confirm that with a test.

We then instantiate bird2 with only a name and test as well, remember that when we don't set locomotion behavior as it defaults to false, as seen in the last two assertions.

7. Inspecting Fields

Previously, we only inspected the names of fields, in this section, we will show how toget and set their values at runtime.

There are two main methods used to inspect fields of a class at runtime: getFields() and getField(fieldName).

The getFields() method returns all accessible public fields of the class in question. It will return all the public fields in both the class and all superclasses.

For instance, when we call this method on the Bird class, we will only get the CATEGORY field of its superclass, Animal, since Bird itself does not declare any public fields:

@Test public void givenClass_whenGetsPublicFields_thenCorrect() { Class birdClass = Class.forName("com.baeldung.reflection.Bird"); Field[] fields = birdClass.getFields(); assertEquals(1, fields.length); assertEquals("CATEGORY", fields[0].getName()); }

This method also has a variant called getField which returns only one Field object by taking the name of the field:

@Test public void givenClass_whenGetsPublicFieldByName_thenCorrect() { Class birdClass = Class.forName("com.baeldung.reflection.Bird"); Field field = birdClass.getField("CATEGORY"); assertEquals("CATEGORY", field.getName()); }

We are not able to access private fields declared in superclasses and not declared in the child class. This is why we are not able to access the name field.

However, we can inspect private fields declared in the class we are dealing with by calling the getDeclaredFields method:

@Test public void givenClass_whenGetsDeclaredFields_thenCorrect(){ Class birdClass = Class.forName("com.baeldung.reflection.Bird"); Field[] fields = birdClass.getDeclaredFields(); assertEquals(1, fields.length); assertEquals("walks", fields[0].getName()); }

We can also use its other variant in case we know the name of the field:

@Test public void givenClass_whenGetsFieldsByName_thenCorrect() { Class birdClass = Class.forName("com.baeldung.reflection.Bird"); Field field = birdClass.getDeclaredField("walks"); assertEquals("walks", field.getName()); }

If we get the name of the field wrong or type in an in-existent field, we will get a NoSuchFieldException.

We get the field type as follows:

@Test public void givenClassField_whenGetsType_thenCorrect() { Field field = Class.forName("com.baeldung.reflection.Bird") .getDeclaredField("walks"); Class fieldClass = field.getType(); assertEquals("boolean", fieldClass.getSimpleName()); }

Next, we will look at how to access field values and modify them. To be able to get the value of a field, let alone setting it, we must first set it's accessible by calling setAccessible method on the Field object and pass boolean true to it:

@Test public void givenClassField_whenSetsAndGetsValue_thenCorrect() { Class birdClass = Class.forName("com.baeldung.reflection.Bird"); Bird bird = (Bird) birdClass.getConstructor().newInstance(); Field field = birdClass.getDeclaredField("walks"); field.setAccessible(true); assertFalse(field.getBoolean(bird)); assertFalse(bird.walks()); field.set(bird, true); assertTrue(field.getBoolean(bird)); assertTrue(bird.walks()); }

In the above test, we ascertain that indeed the value of the walks field is false before setting it to true.

Notice how we use the Field object to set and get values by passing it the instance of the class we are dealing with and possibly the new value we want the field to have in that object.

One important thing to note about Field objects is that when it is declared as public static, then we don't need an instance of the class containing them, we can just pass null in its place and still obtain the default value of the field, like so:

@Test public void givenClassField_whenGetsAndSetsWithNull_thenCorrect(){ Class birdClass = Class.forName("com.baeldung.reflection.Bird"); Field field = birdClass.getField("CATEGORY"); field.setAccessible(true); assertEquals("domestic", field.get(null)); }

8. Inspecting Methods

In a previous example, we used reflection only to inspect method names. However, Java reflection is more powerful than that.

With Java reflection, we can invoke methods at runtime and pass them their required parameters, just like we did for constructors. Similarly, we can also invoke overloaded methods by specifying parameter types of each.

Just like fields, there are two main methods that we use for retrieving class methods. The getMethods method returns an array of all public methods of the class and superclasses.

This means that with this method, we can get public methods of the java.lang.Object class like toString, hashCode, and notifyAll:

@Test public void givenClass_whenGetsAllPublicMethods_thenCorrect(){ Class birdClass = Class.forName("com.baeldung.reflection.Bird"); Method[] methods = birdClass.getMethods(); List methodNames = getMethodNames(methods); assertTrue(methodNames.containsAll(Arrays .asList("equals", "notifyAll", "hashCode", "walks", "eats", "toString"))); }

To get only public methods of the class we are interested in, we have to use getDeclaredMethods method:

@Test public void givenClass_whenGetsOnlyDeclaredMethods_thenCorrect(){ Class birdClass = Class.forName("com.baeldung.reflection.Bird"); List actualMethodNames = getMethodNames(birdClass.getDeclaredMethods()); List expectedMethodNames = Arrays .asList("setWalks", "walks", "getSound", "eats"); assertEquals(expectedMethodNames.size(), actualMethodNames.size()); assertTrue(expectedMethodNames.containsAll(actualMethodNames)); assertTrue(actualMethodNames.containsAll(expectedMethodNames)); }

Each of these methods has the singular variation which returns a single Method object whose name we know:

@Test public void givenMethodName_whenGetsMethod_thenCorrect() throws Exception { Bird bird = new Bird(); Method walksMethod = bird.getClass().getDeclaredMethod("walks"); Method setWalksMethod = bird.getClass().getDeclaredMethod("setWalks", boolean.class); assertTrue(walksMethod.canAccess(bird)); assertTrue(setWalksMethod.canAccess(bird)); }

Notice how we retrieve individual methods and specify what parameter types they take. Those that don't take parameter types are retrieved with an empty variable argument, leaving us with only a single argument, the method name.

Next, we will show how to invoke a method at runtime. We know by default that the walks attribute of the Bird class is false, we want to call its setWalks method and set it to true:

@Test public void givenMethod_whenInvokes_thenCorrect() { Class birdClass = Class.forName("com.baeldung.reflection.Bird"); Bird bird = (Bird) birdClass.getConstructor().newInstance(); Method setWalksMethod = birdClass.getDeclaredMethod("setWalks", boolean.class); Method walksMethod = birdClass.getDeclaredMethod("walks"); boolean walks = (boolean) walksMethod.invoke(bird); assertFalse(walks); assertFalse(bird.walks()); setWalksMethod.invoke(bird, true); boolean walks2 = (boolean) walksMethod.invoke(bird); assertTrue(walks2); assertTrue(bird.walks()); }

Забележете как първо извикваме метода на разходките и прехвърляме типа връщане към подходящия тип данни и след това проверяваме стойността му. След това по-късно извикваме метода setWalks , за да променим тази стойност и да тестваме отново.

9. Заключение

В този урок ние разгледахме Java Reflection API и разгледахме как да го използваме за проверка на класове, интерфейси, полета и методи по време на изпълнение, без предварително знание за техните вътрешни елементи по време на компилиране.

Пълният изходен код и примери за този урок могат да бъдат намерени в GitHub.