Refleksja (Reflection)
Spis treści
Refleksja to mechanizm języka Java, który umożliwia na odczyt i modyfikację informacji o klasach. Dzięki refleksji możesz np. odczytać nazwy pól i metod zapisanych w klasie, a nawet odwołać się do prywatnych pól klasy.
W ramach tej lekcji zobaczymy jak:
- wyświetlić wszystkie pola w klasie i zmienić ich wartość dla wybranego obiektu
- wyświetlić wszystkie metody w klasie i je wywołać
- wywołać konstruktor
Bazować będziemy na następującej klasie:
package pl.javastart.examples.reflection;
public class User {
public String firstName;
private String lastName;
private int age;
public User() {
}
public User(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
if (lastName == null || lastName.length() < 3) {
throw new RuntimeException("Nazwisko powinno mieć min. 3 znaki");
}
this.lastName = lastName;
}
public void setLastName(String lastName, String secondPart) {
this.lastName = lastName + secondPart;
}
@Override
public String toString() {
final StringBuffer sb = new StringBuffer("User{");
sb.append("firstName='").append(firstName).append('\'');
sb.append(", lastName='").append(lastName).append('\'');
sb.append(", age='").append(age).append('\'');
sb.append('}');
return sb.toString();
}
}
Mamy tutaj zarówno pola publiczne, jak i prywatne. Dwa konstruktory oraz gettery oraz settery. Dodatkowo dodana jest metoda toString(), która pozwala na wyświetlenie zawartości tej klasy.
Wyświetlenie wszystkich pól w klasie
Zacznijmy od wyświetlenia wszystkich pól w klasie User
. W tym celu najpierw potrzebny będzie obiekt klasy Class
, a z którego możemy pobrać pola Field
za pomocą getFields()
:
public class Main {
public static void main(String[] args) {
User user = new User();
user.firstName = "Jan";
user.setLastName("Kowalski");
Class<? extends User> userClass = user.getClass();
Field[] fields = userClass.getFields();
for (Field field : fields) {
System.out.println(field);
}
}
}
Taki fragment wyświetli tylko jedno pole:
public java.lang.String pl.javastart.examples.reflection.User.firstName
Dzieje się tak dlatego, ponieważ firstName
jest jedynym publicznym polem. Mechanizm refleksji pozwala dostać się też do prywatnych pól. W tym celu należy użyć getDeclaredFields()
.
public static void main(String[] args) {
User user = new User();
user.firstName = "Jan";
user.setLastName("Kowalski");
Class<? extends User> userClass = user.getClass();
Field[] fields = userClass.getDeclaredFields();
for (Field field : fields) {
System.out.println(field);
}
}
W wyniku otrzymamy wszystkie 3 pola:
public java.lang.String pl.javastart.examples.reflection.User.firstName
private java.lang.String pl.javastart.examples.reflection.User.lastName
private int pl.javastart.examples.reflection.User.age
Wyświetlenie pól oraz ich wartości
No dobrze, teraz spróbujmy jeszcze wyświetlić wartości tych pól. W tym celu należy skorzystać z metody Field.get(Object)
jako argument podając obiekt, którego wartość nas interesuje. Warto w tym momencie zauważyć, że samo Field
dotyczy się tylko i wyłącznie klasy i nie ma pojęcia o obiektach.
User user = new User();
user.firstName = "Jan";
user.setLastName("Kowalski");
Class<? extends User> userClass = user.getClass();
Field[] fields = userClass.getDeclaredFields();
for (Field field : fields) {
System.out.println(field.getName() + " -> " + field.get(user));
}
Taka próba wywołania zakończy się jednak wyjątkiem:
firstName -> Jan
Exception in thread "main" java.lang.IllegalAccessException: class pl.javastart.examples.reflection.Main cannot access a member of class pl.javastart.examples.reflection.User with modifiers "private"
at java.base/jdk.internal.reflect.Reflection.newIllegalAccessException(Reflection.java:394)
at java.base/java.lang.reflect.AccessibleObject.checkAccess(AccessibleObject.java:709)
at java.base/java.lang.reflect.Field.checkAccess(Field.java:1154)
at java.base/java.lang.reflect.Field.get(Field.java:439)
at pl.javastart.examples.reflection.Main.main(Main.java:17)
Jak widać udało się wyświetlić firstName
, ale przy kolejnym polu wystąpił wyjątek. Dzieje się tak, ponieważ domyślnie nie ma dostępu do pól prywatnych. Możemy to jednak zmienić za pomocą Field.setAccessible(true)
public static void main(String[] args) throws IllegalAccessException {
User user = new User();
user.firstName = "Jan";
user.setLastName("Kowalski");
Class<? extends User> userClass = user.getClass();
Field[] fields = userClass.getDeclaredFields();
for (Field field : fields) {
field.setAccessible(true); // dostęp do metod prywatnych
System.out.println(field.getName() + " -> " + field.get(user));
}
}
Tym razem wyświetlają się wszystkie pola wraz z wartościami:
firstName -> Jan
lastName -> Kowalski
age -> 0
Wywoływanie metod
Oprócz dostępu do pól mamy również bardzo analogiczny dostęp do metod. Możemy wyświetlić wszystkie dostępne metody oraz je wywołać. Spróbujmy w takim razie wywołać metodę setLastName
korzystając z refleksji.
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
User user = new User();
user.firstName = "Jan";
user.setLastName("Kowalski");
Class<? extends User> userClass = user.getClass();
Method setLastNameMethod = userClass.getMethod("setLastName", String.class);
setLastNameMethod.invoke(user, "Nowak");
System.out.println(user);
}
Kluczowe są tutaj dwie metody:
Class.getMethod
- jako parametr przyjmuje nazwę metody oraz argumenty tej metody (w naszym przypadkusetLastName
przyjmuje tylko jedenString
)Method.invoke
- wywołuje metodę. Jako pierwszy argument przyjmuje informację o obiekcie, na którym wywołać metodę, natomiast drugim argumentem jest wartość, która ma zostać przekazana jako parametr do metody
Ostatecznie wynik jest następujący:
User{firstName='Jan', lastName='Nowak', age='0'}
Warto w tym momencie zaznaczyć, że gdyby metoda była prywatna to należy użyć metody getDeclaredMethod
oraz ustawić setAccessible(true)
w metodzie.
Wywoływanie konstruktorów
Wywoływanie konstruktorów jest bardzo podobne do uruchamiania metod. Zaczynamy od pobrania interesującego nas konstruktora za pomocą Class.getContructor(<argumenty>)
, a następnie wywołujemy go Constructor.newInstance
podając tyle argumentów ile przy pobieraniu konstruktora.
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException {
Class<? extends User> userClass = User.class;
Constructor<? extends User> constructor = userClass.getConstructor();// kontruktor bezargumentowy
Constructor<? extends User> constructor1 = userClass.getConstructor(String.class, String.class);// konstruktor przyjmujący imię i nazwisko
User user1 = constructor.newInstance();
System.out.println(user1);
User user2 = constructor1.newInstance("Anna", "Nowak");
System.out.println(user2);
}
W powyższym przykładzie constructor1
jest bezargumentowy, constructor2
oczekuje dwóch parametrów. Wynik wywołania jest następujący:
User{firstName='null', lastName='null', age='0'}
User{firstName='Anna', lastName='Nowak', age='0'}
Własny toString()
Korzystając w wiedzy opisanej powyżej możemy pokusić się stworzenie własnej metody toString()
, która wypisze wszystkie pola w zadanym obiekcie. Wystarczy zacząć od pobrania klasy obiektu, a potem jest już z górki:
private static void printObject(Object object) throws IllegalAccessException {
Class<?> clazz = object.getClass();
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append(clazz.getSimpleName());
stringBuilder.append("{");
Field[] declaredFields = clazz.getDeclaredFields();
for (Field declaredField : declaredFields) {
declaredField.setAccessible(true);
stringBuilder.append(declaredField.getName());
stringBuilder.append("='");
stringBuilder.append(declaredField.get(object));
stringBuilder.append("', ");
}
String substring = stringBuilder.substring(0, stringBuilder.length() - 2);
System.out.println(substring + "}");
}
Kod źródłowy
Kod źródłowy można znaleźć tutaj:
Dyskusja i komentarze
Masz pytania do tego wpisu? Może chcesz się podzielić spostrzeżeniami? Zapraszamy dyskusji na naszej grupie na Facebooku.