Programowanie generyczne pozwala na ujednolicenie Twojego kodu w wielu obszarach. Podobnie działa mechanizm refleksji. Tak tak jak w matematyce, aby się nie powtarzać możesz skorzystać z wielu symboli i wyrażeń*, tak samo w programowaniu możesz ułatwić sobie życie, stawiając na abstrakcje, którą wykorzystasz wielokrotnie, zamiast implementując prawie taką samą logikę w kilku miejscach na raz. Zysk jest oczywisty, bo w przypadku chęci wprowadzenia zmian, nie musisz dokonywać ich w wielu miejscach.
Mechanizm refleksji
Korzystanie z mechanizmu refleksji Javy, pozwoli Ci sięgnąć do składników klasy, nawet jeśli nie wiesz, jak dokładnie wygląda jej implementacja. Spójrz na poniższy przykład.
Pierwsza napisana przeze mnie klasa Shop, to zwykła klasa POJO, zawierająca pola, konstruktor z parametrami, gettery i settery oraz jedną metodę countTotalValue.
public class Shop { private String name; private String address; private int realEstateValue; private int annualIncome; public Shop(String name, String address, int realEstateValue, int annualIncome) { this.name = name; this.address = address; this.realEstateValue = realEstateValue; this.annualIncome = annualIncome; } public int countTotalValue() { return realEstateValue + annualIncome; } // gettery i settery
Korzystanie z pakietu reflect
Następnie w innej klasie (załóżmy w metodzie main) chciałbym wyświetlić podstawowe informacje o klasie, takie jak jej nazwa, metody, itp.). Cztery najczęściej używane klasy z pakietu java.lang.reflect to:
- Class – zawiera podstawowe informacje o klasie, takie jak: jej nazwa, pakiet, dostęp do jej składowych oraz modyfikatora dostępu. Umożliwia też tworzenie jej nowego obiektu lub wywoływanie odpowiednich metod. Pozwala nawet na wyświetlenie zaimplementowanych interfejsów oraz użytych adnotacji.
- Method – podobnie jak wyżej, tylko tutaj można znaleźć informacje o zwracanym typie czy parametrze.
- Constructor – informacje odnośnie konstruktora.
- i Field – podobnie jak wyżej.
Pobieranie metadanych
W pierwszej linii kodu pokazuję, z której klasy chcę korzystać. Następnie jeśli taka klasa istnieje, wyświetlam w pętli jej wszystkie metody, a raczej ich zawartość (modyfikatory dostępu, zwracany typ, nazwa funkcji oraz tablica użytych parametrów). Na samym końcu sięgam po klasę rodzica, używając getSuperclass. W ten sposób jestem wstanie przeanalizować wszystkie klasy dostępne w hierarchii dziedziczenia. Przypominam, że w Javie niejawnie każda klasa dziedziczy po klasie Object, więc na samym końcu na konsoli zostaną wyświetlone informacje o niej.
Class<?> classByReflection = Class.forName(Shop.class.getName()); while(classByReflection != null) { for (Method method : classByReflection.getDeclaredMethods()) { System.out.println( Modifier.toString(method.getModifiers()) + " " + method.getReturnType().getCanonicalName() + " " + method.getName() + " " + Arrays.toString(method.getParameters())); } classByReflection = classByReflection.getSuperclass(); }
Efekt na ekranie monitora:
public java.lang.String getAddress [] public java.lang.String getName [] public void setName [java.lang.String arg0] public int countTotalValue [] public int getAnnualIncome [] public void setAnnualIncome [int arg0] public void setAddress [java.lang.String arg0] public int getRealEstateValue [] public void setRealEstateValue [int arg0] protected void finalize [] public final void wait [] public final void wait [long arg0, int arg1] public final native void wait [long arg0] public boolean equals [java.lang.Object arg0] public java.lang.String toString [] public native int hashCode [] public final native java.lang.Class getClass [] protected native java.lang.Object clone [] public final native void notify [] public final native void notifyAll [] private static native void registerNatives []
Ten kod to tylko prosty przykład, jak można wykorzystać sam mechanizm refleksji, który jest dużo bardziej rozbudowany niż w tym wpisie. W praktyce będziesz korzystać z niego bardzo rzadko. Chciałem tylko zasygnalizować Ci, że istnieje takie rozwiązanie. W rzeczywistości refleksja, to opcja przydatna głównie, gdy piszesz własny framework lub adnotację.
Niestety używanie tego API ma pewne wady, oto kilka z nich:
- mniejsza wydajność – brak optymalizacji wynikającej z samego mechanizmu JVM
- łamanie zasady hermetyzacji – dostęp do części składowych klasy o dostępie prywatnym
- działanie w czasie rzeczywistym (runtime) – główna zaleta refleksji może być też wadą, w przypadku gdy kod jest w jakiś sposób zabezpieczony ze względów bezpieczeństwa
- niższa prostota – chyba największą wadą Reflection API jest zrozumienie takiego kodu oraz jego debuggowanie.
Kilka znanych frameworków, korzystających z refleksji: JUnit, Spring (np. wstrzykiwanie zależności), Hibernate.
*Wykaz symboli i znaków w matematyce: https://www.megamatma.pl/uczniowie/wzory/symbole/symbole
Kod do lekcji: https://github.com/developeronthego/java-advanced/tree/master/src/main/java/advanced/lesson5
Jeśli ten zapis: Class<?> jest dla Ciebie nieznany, przejrzyj informacje o typie generycznym wildcard: Java #47: typ wieloznaczny (wildcard)