CodeGym /Java blog /Véletlen /Reflection API: Reflection. A Java sötét oldala
John Squirrels
Szint
San Francisco

Reflection API: Reflection. A Java sötét oldala

Megjelent a csoportban
Ü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.

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.
A tükrözést szinte minden modern Java technológia alkalmazza. Nehéz elképzelni, hogy a Java, mint platform, gondolkodás nélkül elérhette volna ilyen széles körű elterjedését. Valószínűleg nem lett volna. Most, hogy általánosan ismeri a reflexiót, mint elméleti fogalmat, térjünk rá a gyakorlati alkalmazására! Nem fogjuk megtanulni a Reflection API összes módszerét – csak azokat, amelyekkel a gyakorlatban találkozni fog. Mivel a reflexió magában foglalja az osztályokkal való munkát, kezdjük egy egyszerű osztállyal, melynek neve 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 nemMyClassegy 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 MyClassosztá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, aholStringa kívánt mező neve. Jegyzet: getFields()és getDeclaredFields()ne adja vissza szülőosztály mezőit! Nagy. Kaptunk egy Fieldobjektumot , 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 Fieldobjektum get(Object)metódusának meghívásával, ahol Objectaz 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/catchblokkra é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 MyClassmá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 Methodhívjuk a invoke(Object, Args)metódust, ahol Objectszintén az osztály egy példánya MyClass. Argsezek a metódus érvei, bár a miénknek nincs. Most a függvényt használjuk printDatainformá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 .
Reflection API: Reflection.  A Java sötét oldala – 2
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):

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 ClassLoadera , 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 ClassLoadera 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 Stringannak az osztálynak a neve, amelynek leírására szükségünk van. Az objektum beszerzése után Сlassa metódus meghívása a leírás alapján létrehozott objektumot newInstance()ad vissza . ObjectMár csak az van hátra, hogy ezt az objektumot ellássukMyClassosztá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 invokehí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:

Hozzászólások
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION