CodeGym /Kursy /JAVA 25 SELF /Tworzenie obiektów za pomocą refleksji

Tworzenie obiektów za pomocą refleksji

JAVA 25 SELF
Poziom 62 , Lekcja 3
Dostępny

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ć
new User()
Najszybsza Pełna Zawsze, gdy to możliwe
Constructor.newInstance()
Wolna Utracona Frameworki, wtyczki
MethodHandle
Ś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.

Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION