Üdvözlet, fiatal Padawan. Ebben a cikkben az Erőről fogok mesélni, egy olyan erőről, amelyet a Java programozók csak lehetetlennek tűnő helyzetekben használnak. A Java sötét oldala a Reflection API. A Java nyelvben a tükrözés a Java Reflection API segítségével valósul meg.
A csomaghierarchiámban a MyClass teljes neve "reflection.MyClass" lenne. Van egy egyszerű módszer is az osztály nevének megtanulására (az osztály nevét karakterláncként adja vissza):
Mi az a Java tükrözés?
Van egy rövid, pontos és népszerű meghatározás az interneten. A reflexió ( a késői latin reflexio szóból – visszafordulni ) egy olyan mechanizmus, amely a program futása közben feltárja az adatokat. A Reflection lehetővé teszi a mezőkkel, metódusokkal és osztálykonstruktorokkal kapcsolatos információk felfedezését. A Reflection lehetővé teszi, hogy olyan típusokkal dolgozzon, amelyek nem voltak jelen a fordítási időben, de a futási idő alatt elérhetővé váltak. A tükrözés és a hibainformációk kiadásának logikailag konzisztens modellje lehetővé teszi a helyes dinamikus kód létrehozását. Más szóval, ha megérti, hogyan működik a reflexió a Java-ban, számos csodálatos lehetőséget nyit meg az Ön számára. Szó szerint zsonglőrködhet az osztályokkal és azok összetevőivel. Íme egy alapvető lista arról, hogy mit tesz lehetővé a tükrözés:- Tanulja meg/határozza meg egy objektum osztályát;
- Információkat szerezhet egy osztály módosítóiról, mezőiről, metódusairól, konstansairól, konstruktorairól és szuperosztályairól;
- Ismerje meg, milyen metódusok tartoznak a megvalósított interfész(ek)hez;
- Hozzon létre egy példányt egy olyan osztályból, amelynek osztályneve futási idejéig ismeretlen;
- Az objektum mezőinek értékeinek lekérése és beállítása név szerint;
- Egy objektum metódusának hívása név szerint.
MyClass
:
public class MyClass {
private int number;
private String name = "default";
// public MyClass(int number, String name) {
// this.number = number;
// this.name = name;
// }
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
public void setName(String name) {
this.name = name;
}
private void printData(){
System.out.println(number + name);
}
}
Amint látja, ez egy nagyon alap osztály. A paraméterekkel rendelkező konstruktor szándékosan ki van írva. Erre később még visszatérünk. Ha figyelmesen megnézte az óra tartalmát, valószínűleg észrevette, hogy a névmezőnél hiányzik a getter . Magát a névmezőt a privát hozzáférés módosító jelöli : magán az osztályon kívül nem tudjuk elérni, ami azt jelenti, hogy nem tudjuk lekérni az értékét. – Szóval mi a probléma ? te mondod. "Adjon hozzá gettert vagy módosítsa a hozzáférés-módosítót". És igazad lenne, hacsak nemMyClass
egy lefordított AAR-könyvtárban vagy egy másik privát modulban volt, ahol nem volt lehetőség változtatásokra. A gyakorlatban ez mindig megtörténik. És néhány gondatlan programozó egyszerűen elfelejtett gettert írni . Itt az ideje, hogy emlékezzünk a reflexióra! Próbáljunk meg eljutni az MyClass
osztály privát név mezőjéhez:
public static void main(String[] args) {
MyClass myClass = new MyClass();
int number = myClass.getNumber();
String name = null; // No getter =(
System.out.println(number + name); // Output: 0null
try {
Field field = myClass.getClass().getDeclaredField("name");
field.setAccessible(true);
name = (String) field.get(myClass);
} catch (NoSuchFieldException | IllegalAccessException e) {
e.printStackTrace();
}
System.out.println(number + name); // Output: 0default
}
Elemezzük a történteket. A Java-ban van egy csodálatos osztály, az úgynevezett Class
. Osztályokat és interfészeket jelenít meg egy futtatható Java alkalmazásban. Nem térünk ki a Class
és közötti kapcsolatra ClassLoader
, mivel ennek a cikknek nem ez a témája. Ezután az osztály mezőinek lekéréséhez meg kell hívnia a getFields()
metódust. Ez a metódus visszaadja ennek az osztálynak az összes elérhető mezőjét. Ez nálunk nem működik, mert a mi mezőnk privátgetDeclaredFields()
, ezért a módszert használjuk . Ez a metódus osztálymezők tömbjét is visszaadja, de most magán és védett mezőket is tartalmaz. Ebben az esetben tudjuk a minket érdeklő mező nevét, így használhatjuk a getDeclaredField(String)
módszert, aholString
a kívánt mező neve. Jegyzet: getFields()
és getDeclaredFields()
ne adja vissza szülőosztály mezőit! Nagy. Kaptunk egy Field
objektumot , amely a nevünkre utal . Mivel a mező nem volt nyilvános , hozzáférést kell biztosítanunk a használathoz. A setAccessible(true)
módszer lehetővé teszi, hogy továbblépjünk. Most a névmező teljes ellenőrzésünk alatt áll! Értékét lekérheti az Field
objektum get(Object)
metódusának meghívásával, ahol Object
az osztályunk egy példánya MyClass
. Átalakítjuk a típust a névváltozónkraString
, és hozzárendeljük az értéket . Ha nem találunk beállítót a névmező új értékének beállításához, használhatja a beállítási módszert:
field.set(myClass, (String) "new value");
Gratulálunk! Éppen most sajátította el a reflexió alapjait, és belép egy privát területre! Ügyeljen a try/catch
blokkra és a kezelt kivételek típusára. Az IDE megmondja, hogy a jelenlétük önmagában is szükséges, de a nevükből egyértelműen megtudhatja, miért vannak itt. Továbblépni! Amint azt valószínűleg észrevette, osztályunkban MyClass
már van egy módszer az osztályadatokkal kapcsolatos információk megjelenítésére:
private void printData(){
System.out.println(number + name);
}
De ez a programozó itt is hagyta az ujjlenyomatait. A metódus rendelkezik privát hozzáférés módosítóval, és minden alkalommal saját kódot kell írnunk az adatok megjelenítéséhez. Micsoda rendetlenség. Hová tűnt a tükörképünk? Írja be a következő függvényt:
public static void printData(Object myClass){
try {
Method method = myClass.getClass().getDeclaredMethod("printData");
method.setAccessible(true);
method.invoke(myClass);
} catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
e.printStackTrace();
}
}
Az eljárás itt nagyjából megegyezik a mező lekéréséhez használt eljárással. A kívánt metódust név szerint érjük el, és hozzáférést biztosítunk hozzá. És az objektumon Method
hívjuk a invoke(Object, Args)
metódust, ahol Object
szintén az osztály egy példánya MyClass
. Args
ezek a metódus érvei, bár a miénknek nincs. Most a függvényt használjuk printData
információk megjelenítésére:
public static void main(String[] args) {
MyClass myClass = new MyClass();
int number = myClass.getNumber();
String name = null; //?
printData(myClass); // Output: 0default
try {
Field field = myClass.getClass().getDeclaredField("name");
field.setAccessible(true);
field.set(myClass, (String) "new value");
name = (String) field.get(myClass);
} catch (NoSuchFieldException | IllegalAccessException e) {
e.printStackTrace();
}
printData(myClass);// Output: 0new value
}
Hurrá! Most hozzáférünk az osztály privát metódusához. De mi van akkor, ha a metódusnak vannak érvei, és miért van kiírva a konstruktor? Mindent a maga idejében. A kezdeti definícióból egyértelműen kiderül, hogy a reflektálással futás közben (a program futása közben) létrehozhatunk egy osztály példányait ! Létrehozhatunk objektumot az osztály teljes nevével. Az osztály teljes neve az osztály neve, beleértve a csomag elérési útját .

MyClass.class.getName()
Használjuk a Java tükrözést az osztály példányának létrehozásához:
public static void main(String[] args) {
MyClass myClass = null;
try {
Class clazz = Class.forName(MyClass.class.getName());
myClass = (MyClass) clazz.newInstance();
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
e.printStackTrace();
}
System.out.println(myClass); // Output: created object reflection.MyClass@60e53b93
}
Amikor egy Java-alkalmazás elindul, nem minden osztály töltődik be a JVM-be. Ha a kód nem hivatkozik az osztályra MyClass
, akkor ClassLoader
a , amely az osztályok JVM-be való betöltéséért felelős, soha nem fogja betölteni az osztályt. Ez azt jelenti, hogy erőltetnie kell ClassLoader
a betöltését, és egy változó formájában osztályleírást kell kapnia Class
. Ezért van a forName(String)
metódusunk, ahol String
annak az osztálynak a neve, amelynek leírására szükségünk van. Az objektum beszerzése után Сlass
a metódus meghívása a leírás alapján létrehozott objektumot newInstance()
ad vissza . Object
Már csak az van hátra, hogy ezt az objektumot ellássukMyClass
osztály. Menő! Nehéz volt, de remélem érthető. Most szó szerint egy sorban létrehozhatunk egy osztály példányát! Sajnos a leírt megközelítés csak az alapértelmezett konstruktorral működik (paraméterek nélkül). Hogyan lehet paraméterekkel metódusokat és konstruktorokat hívni? Itt az ideje, hogy megjegyzéseket tegyünk konstruktorunkról. Ahogy az várható volt, newInstance()
nem találja az alapértelmezett konstruktort, és már nem működik. Írjuk át az osztály példányosítását:
public static void main(String[] args) {
MyClass myClass = null;
try {
Class clazz = Class.forName(MyClass.class.getName());
Class[] params = {int.class, String.class};
myClass = (MyClass) clazz.getConstructor(params).newInstance(1, "default2");
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {
e.printStackTrace();
}
System.out.println(myClass);// Output: created object reflection.MyClass@60e53b93
}
A getConstructors()
metódust az osztálydefinícióból kell meghívni az osztálykonstruktorok beszerzéséhez, majd getParameterTypes()
a konstruktor paramétereinek lekéréséhez:
Constructor[] constructors = clazz.getConstructors();
for (Constructor constructor : constructors) {
Class[] paramTypes = constructor.getParameterTypes();
for (Class paramType : paramTypes) {
System.out.print(paramType.getName() + " ");
}
System.out.println();
}
Ezzel megkapjuk az összes konstruktort és paramétereiket. Példámban konkrét, korábban ismert paraméterekkel rendelkező konkrét konstruktorra utalok. Ennek a konstruktornak a meghívásához pedig azt a metódust használjuk newInstance
, amelynek átadjuk ezen paraméterek értékeit. Ugyanez lesz a invoke
hívó módszerek használatakor is. Ez felveti a kérdést: mikor jön jól a konstruktorok reflektálással történő meghívása? Ahogy már az elején említettük, a modern Java technológiák nem boldogulnak a Java Reflection API nélkül. Például a Dependency Injection (DI), amely az annotációkat a metódusok és konstruktorok tükrözésével kombinálja a népszerű Darer létrehozásához.könyvtár Android fejlesztéshez. Miután elolvasta ezt a cikket, magabiztosan úgy gondolhatja, hogy a Java Reflection API módszereiben tanult. Nem hiába nevezik a tükröződést a Java sötét oldalának. Teljesen megtöri az OOP paradigmát. A Java-ban a beágyazás elrejti és korlátozza mások hozzáférését bizonyos programösszetevőkhöz. Amikor a privát módosítót használjuk, azt a mezőt csak az osztályon belülről kívánjuk elérni, ahol létezik. És erre az elvre építjük a program későbbi architektúráját. Ebben a cikkben azt láthattuk, hogyan használhatja a reflexiót, hogy bárhová kikényszerítse magát. A kreatív tervezési minta Singletonjó példa erre, mint építészeti megoldásra. Az alapötlet az, hogy az ezt a mintát megvalósító osztálynak csak egy példánya lesz a teljes program végrehajtása során. Ezt úgy érheti el, hogy hozzáadja a privát hozzáférés módosítóját az alapértelmezett konstruktorhoz. És nagyon rossz lenne, ha egy programozó reflexiót használna és több példányt hozna létre ilyen osztályokból. Egyébként nemrég hallottam egy munkatársamtól egy nagyon érdekes kérdést: örökölhető-e egy olyan osztály, amely megvalósítja a Singleton-mintát? Lehetséges, hogy ebben az esetben még a reflexió is tehetetlen lenne? Hagyja visszajelzését a cikkről és válaszát az alábbi megjegyzésekben, és tegye fel saját kérdéseit ott!
További olvasnivalók: |
---|
GO TO FULL VERSION