1. Tworzenie obiektów przez refleksję
Czasem musimy tworzyć obiekty, nie znając ich typu w czasie kompilacji. Na przykład nazwa klasy przychodzi z konfiguracji albo piszemy uniwersalny framework (serializator, kontener DI). W zwykłym kodzie napisalibyśmy:
User user = new User("Ivan", 25);
Ale co, jeśli nie wiemy, że klasa nazywa się User, i nie znamy parametrów jej konstruktora? Tutaj pomaga refleksja.
Przestarzały sposób: Class.newInstance()
Kiedyś w Javie istniała metoda Class.newInstance(), tworząca obiekt przez publiczny konstruktor bez parametrów:
Class<?> clazz = Class.forName("com.example.User");
Object obj = clazz.newInstance();
WAŻNE: Ta metoda jest oznaczona jako deprecated od Java 9 i nie jest rekomendowana. Nie pozwala wybrać konstruktora, ukrywa przyczyny błędów i wymaga wyłącznie public konstruktora bez parametrów.
Nowoczesny sposób: przez konstruktory
W rzeczywistym kodzie nie zawsze istnieje public konstruktor bez parametrów. Aktualne podejście – pobrać potrzebny konstruktor przez getConstructor(...) lub getDeclaredConstructor(...), a następnie wywołać na nim newInstance(...).
Przykład: tworzenie obiektu z parametrami
public class User {
private String name;
private int age;
public User(String name, int age) {
this.name = name;
this.age = age;
}
}
import java.lang.reflect.Constructor;
Class<?> clazz = Class.forName("User");
// Pobieramy konstruktor z parametrami String, int
Constructor<?> constructor = clazz.getConstructor(String.class, int.class);
// Tworzymy obiekt, przekazujemy parametry
Object user = constructor.newInstance("Ivan", 25);
System.out.println(user); // toString, jeśli zdefiniowano
Co tu się dzieje?
- Pobieramy potrzebny konstruktor po sygnaturze.
- Wywołujemy na nim newInstance(...), przekazując argumenty.
- Otrzymujemy obiekt typu Object (można rzutować do User, jeśli typ jest znany).
A jeśli konstruktor jest prywatny?
Constructor<?> constructor = clazz.getDeclaredConstructor(String.class, int.class);
constructor.setAccessible(true); // Magia! Teraz można wywołać prywatny konstruktor.
Object user = constructor.newInstance("Ivan", 25);
UWAGA: Nie należy nadużywać tego mechanizmu – narusza on enkapsulację. W aplikacjach modułowych (Java 9+) obowiązują również ograniczenia dostępu.
Tablica porównawcza sposobów tworzenia obiektów
| Sposób | Szybkość | Bezpieczeństwo typów | Kiedy używać |
|---|---|---|---|
|
Najszybsza | Pełna | Zawsze, gdy to możliwe |
|
Wolna | Utracona | Frameworki, wtyczki |
|
Średnia | Częściowa | Wydajne biblioteki |
| Fabryki | Szybka | Pełna | Elastyczne tworzenie obiektów |
2. Wywoływanie metod przez refleksję
Możesz wywołać dowolną metodę obiektu, nawet jeśli nie znasz jej nazwy w czasie kompilacji, albo jeśli jest private.
Pobieranie metody
import java.lang.reflect.Method;
Class<?> clazz = user.getClass();
// Pobieramy metodę public po nazwie i typach parametrów
Method method = clazz.getMethod("getName"); // Bez parametrów
Object result = method.invoke(user); // Wywołanie metody bez parametrów
System.out.println(result); // Wypisze nazwę użytkownika
Wywołanie metody z parametrami
Method setName = clazz.getMethod("setName", String.class);
setName.invoke(user, "Petr"); // Ustawiamy nową nazwę
Wywołanie prywatnej metody
Method secret = clazz.getDeclaredMethod("secretMethod", int.class);
secret.setAccessible(true); // Zdejmujemy ochronę
Object secretResult = secret.invoke(user, 123);
Ważne: Wszystkie parametry są przekazywane jako tablica obiektów (varargs). Jeśli metoda coś zwraca – wynik przyjdzie jako Object.
3. Dostęp do pól przez refleksję
Czasem trzeba odczytać lub zmienić wartość pola, nawet jeśli jest private (np. przy serializacji lub testach).
Pobieranie pola
import java.lang.reflect.Field;
Class<?> clazz = user.getClass();
Field ageField = clazz.getDeclaredField("age");
ageField.setAccessible(true); // Jeśli pole nie jest public
// Odczyt wartości
Object age = ageField.get(user);
System.out.println("Wiek: " + age);
// Zmiana wartości
ageField.set(user, 42);
System.out.println("Nowy wiek: " + ageField.get(user));
Praca z polami statycznymi
Jeśli pole jest statyczne, przekazuj do get/set zamiast obiektu null:
Field staticField = clazz.getDeclaredField("counter");
staticField.setAccessible(true);
staticField.set(null, 100); // Dla pól static obiekt nie jest potrzebny
4. Ograniczenia i wyjątki
Praca z refleksją może wiązać się z wieloma wyjątkami typu checked. Najczęstsze to:
- ClassNotFoundException – klasa o takiej nazwie nie została znaleziona.
- NoSuchMethodException – brak konstruktora lub metody o takiej sygnaturze.
- NoSuchFieldException – pole nie zostało znalezione.
- IllegalAccessException – brak dostępu do składowej (np. prywatna metoda bez setAccessible(true)).
- InstantiationException – klasa jest abstrakcyjna lub to interfejs; nie można utworzyć obiektu.
- InvocationTargetException – błąd wewnątrz wywoływanego konstruktora lub metody.
Przykład obsługi:
try {
// ... twój kod z refleksją ...
} catch (ReflectiveOperationException e) {
e.printStackTrace();
}
Jeśli szczegóły nie są istotne, wygodniej łapać wspólną nadklasę ReflectiveOperationException (Java 7+).
5. Praktyka: mini‑program „Dynamiczny Instancjator”
Przykładowa klasa do eksperymentów
public class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
private void sayHello() {
System.out.println("Cześć, mam na imię " + name + ", mam " + age + " lat.");
}
}
Dynamiczne tworzenie i manipulacja
import java.lang.reflect.*;
public class ReflectionDemo {
public static void main(String[] args) {
try {
// 1. Pobieramy obiekt Class po nazwie
Class<?> clazz = Class.forName("Person");
// 2. Pobieramy konstruktor i tworzymy obiekt
Constructor<?> constructor = clazz.getConstructor(String.class, int.class);
Object person = constructor.newInstance("Alicja", 30);
// 3. Wywołujemy prywatną metodę sayHello
Method sayHello = clazz.getDeclaredMethod("sayHello");
sayHello.setAccessible(true);
sayHello.invoke(person); // Cześć, mam na imię Alicja, mam 30 lat.
// 4. Zmieniamy prywatne pole name
Field nameField = clazz.getDeclaredField("name");
nameField.setAccessible(true);
nameField.set(person, "Bob");
// 5. Ponownie wywołujemy sayHello
sayHello.invoke(person); // Cześć, mam na imię Bob, mam 30 lat.
} catch (ReflectiveOperationException e) {
e.printStackTrace();
}
}
}
Zwróć uwagę:
- Wszystko odbywa się bez bezpośredniej wiedzy o typie Person w kodzie.
- Można zmieniać prywatne pola i wywoływać prywatne metody (o ile pozwala na to polityka bezpieczeństwa JVM i konfiguracja modułów).
6. Jak to się ma do rzeczywistych aplikacji?
Refleksja leży u podstaw wielu popularnych bibliotek i frameworków:
- JUnit: wyszukiwanie metod z adnotacją @Test, tworzenie instancji testów i wywołania metod.
- Spring: DI, tworzenie beanów, autowiring.
- Jackson, Gson: serializacja/deserializacja pól obiektów.
- Hibernate: dostęp do pól encji, proxy i leniwe ładowanie.
7. Schemat: jak tworzy się obiekt za pomocą refleksji
flowchart TB
A["Nazwa klasy (String)"] --> B["Class.forName"]
B --> C["Class<?>"]
C --> D["getConstructor(...)"]
D --> E["Constructor<?>"]
E --> F["newInstance(...)"]
F --> G["Object"]
8. Typowe błędy przy pracy z refleksją
Błąd nr 1: Nie ten konstruktor. Żądasz konstruktora o niewłaściwej sygnaturze, na przykład getConstructor(String.class) przy istnieniu tylko konstruktora z dwoma parametrami – otrzymasz NoSuchMethodException. Zawsze sprawdzaj sygnatury!
Błąd nr 2: Brak dostępu. Wywołanie prywatnego konstruktora/metody bez setAccessible(true) prowadzi do IllegalAccessException. I pamiętaj: w Java 9+ system modułów może nakładać dodatkowe ograniczenia.
Błąd nr 3: Problemy z typami. Wszystkie parametry i wartości zwracane przez refleksję – to Object. Błędne rzutowanie – ClassCastException.
Błąd nr 4: Nieobsłużone wyjątki. Refleksja rzuca wiele wyjątków typu checked. Obsługuj je, na przykład wspólnym ReflectiveOperationException, w przeciwnym razie kod się nie skompiluje.
Błąd nr 5: Naruszenie enkapsulacji. Bezmyślne użycie setAccessible(true) – „dostęp do cudzej lodówki”. Używaj tylko, gdy naprawdę trzeba, i uwzględniaj wymagania bezpieczeństwa.
GO TO FULL VERSION