JavaRush /Java Blog /Random-TL /Reflection sa Java - Mga Halimbawa ng Paggamit

Reflection sa Java - Mga Halimbawa ng Paggamit

Nai-publish sa grupo
Maaaring nakita mo ang konsepto ng "pagninilay" sa pang-araw-araw na buhay. Kadalasan ang salitang ito ay tumutukoy sa proseso ng pag-aaral sa sarili. Sa programming, ito ay may katulad na kahulugan - ito ay isang mekanismo para sa pagsusuri ng data tungkol sa isang programa, pati na rin ang pagbabago ng istraktura at pag-uugali ng programa sa panahon ng pagpapatupad nito. Ang mahalagang bagay dito ay ginagawa ito sa runtime, hindi sa oras ng pag-compile. Ngunit bakit suriin ang code sa runtime? Nakikita mo na ito :/ Mga halimbawa ng paggamit ng Reflection - 1Ang ideya ng pagninilay ay maaaring hindi agad malinaw sa isang dahilan: hanggang sa sandaling ito, palagi mong alam ang mga klase na iyong pinagtatrabahuhan. Halimbawa, maaari kang magsulat ng isang klase Cat:
package learn.javarush;

public class Cat {

   private String name;
   private int age;

   public Cat(String name, int age) {
       this.name = name;
       this.age = age;
   }

   public void sayMeow() {

       System.out.println("Meow!");
   }

   public void jump() {

       System.out.println("Jump!");
   }

   public String getName() {
       return name;
   }

   public void setName(String name) {
       this.name = name;
   }

   public int getAge() {
       return age;
   }

   public void setAge(int age) {
       this.age = age;
   }

@Override
public String toString() {
   return "Cat{" +
           "name='" + name + '\'' +
           ", age=" + age +
           '}';
}

}
Alam mo ang lahat tungkol dito, makikita mo kung anong mga larangan at pamamaraan ang mayroon ito. Tiyak na maaari kang lumikha ng isang sistema ng mana na may isang karaniwang klase para sa kaginhawahan Animal, kung biglang kailangan ng programa ng iba pang mga klase ng mga hayop. Noong nakaraan, gumawa pa kami ng klase ng klinika sa beterinaryo kung saan maaari kang magpasa ng bagay ng magulang Animal, at gagamutin ng programa ang hayop depende sa kung ito ay aso o pusa. Bagama't hindi masyadong simple ang mga gawaing ito, natututo ang programa ng lahat ng impormasyong kailangan nito tungkol sa mga klase sa oras ng pag-compile. Samakatuwid, kapag main()ipinasa mo ang isang bagay sa isang pamamaraan Catsa mga pamamaraan ng klase ng klinika ng beterinaryo, alam na ng programa na ito ay isang pusa, hindi isang aso. Ngayon isipin natin na tayo ay nahaharap sa isa pang gawain. Ang aming layunin ay magsulat ng isang code analyzer. Kailangan nating lumikha ng isang klase CodeAnalyzerna may iisang pamamaraan - void analyzeClass(Object o). Ang pamamaraang ito ay dapat:
  • tukuyin kung anong klase ang bagay na ipinasa dito at ipakita ang pangalan ng klase sa console;
  • tukuyin ang mga pangalan ng lahat ng mga field ng klase na ito, kabilang ang mga pribado, at ipakita ang mga ito sa console;
  • tukuyin ang mga pangalan ng lahat ng mga pamamaraan ng klase na ito, kabilang ang mga pribado, at ipakita ang mga ito sa console.
Magiging ganito ang hitsura nito:
public class CodeAnalyzer {

   public static void analyzeClass(Object o) {

       //Вывести название класса, к которому принадлежит an object o
       //Вывести названия всех переменных этого класса
       //Вывести названия всех методов этого класса
   }

}
Ngayon ay makikita na ang pagkakaiba sa pagitan ng problemang ito at ng iba pang problemang nalutas mo noon. Sa kasong ito, ang kahirapan ay nakasalalay sa katotohanan na hindi mo alam o ang programa kung ano ang eksaktong ipapasa sa pamamaraan analyzeClass(). Sumulat ka ng isang programa, sisimulan itong gamitin ng ibang mga programmer, na maaaring magpasa ng anuman sa pamamaraang ito - anumang karaniwang klase ng Java o anumang klase na kanilang isinulat. Ang klase na ito ay maaaring magkaroon ng anumang bilang ng mga variable at pamamaraan. Sa madaling salita, sa kasong ito kami (at ang aming programa) ay walang ideya kung anong mga klase ang aming gagawin. Gayunpaman, dapat nating lutasin ang problemang ito. At dito tinutulungan kami ng karaniwang Java library - ang Java Reflection API. Ang Reflection API ay isang malakas na feature ng wika. Ang opisyal na dokumentasyon ng Oracle ay nagsasaad na ang mekanismong ito ay inirerekomenda na gamitin lamang ng mga nakaranasang programmer na lubos na nakakaunawa sa kanilang ginagawa. Malapit mo nang mauunawaan kung bakit bigla kaming binibigyan ng ganitong mga babala nang maaga :) Narito ang isang listahan ng kung ano ang maaaring gawin gamit ang Reflection API:
  1. Alamin/tukuyin ang klase ng isang bagay.
  2. Kumuha ng impormasyon tungkol sa mga modifier ng klase, field, pamamaraan, constant, constructor at superclass.
  3. Alamin kung aling mga pamamaraan ang nabibilang sa ipinatupad na interface/mga interface.
  4. Lumikha ng isang halimbawa ng isang klase kapag ang pangalan ng klase ay hindi kilala hanggang sa ang programa ay naisakatuparan.
  5. Kunin at itakda ang halaga ng isang object field ayon sa pangalan.
  6. Tumawag ng paraan ng isang bagay sa pamamagitan ng pangalan.
Kahanga-hangang listahan, ha? :) Bigyang-pansin:Nagagawa ng mekanismo ng pagmuni-muni ang lahat ng ito "on the fly" anuman ang bagay na ipapasa namin sa aming code analyzer! Tingnan natin ang mga kakayahan ng Reflection API na may mga halimbawa.

Paano malalaman / matukoy ang klase ng isang bagay

Magsimula tayo sa mga pangunahing kaalaman. Ang entry point sa mekanismo ng pagmuni-muni ng Java ay ang Class. Oo, mukhang talagang nakakatawa, ngunit iyon ang para sa pagmumuni-muni :) Gamit ang isang klase Class, una naming tinutukoy ang klase ng anumang bagay na ipinasa sa aming pamamaraan. Subukan natin ito:
import learn.javarush.Cat;

public class CodeAnalyzer {

   public static void analyzeClass(Object o) {
       Class clazz = o.getClass();
       System.out.println(clazz);
   }

   public static void main(String[] args) {

       analyzeClass(new Cat("Barsik", 6));
   }
}
Output ng console:

class learn.javarush.Cat
Bigyang-pansin ang dalawang bagay. Una, sinasadya naming ilagay ang klase Catsa isang hiwalay na pakete. learn.javarush;Ngayon ay makikita mo na getClass()ibinabalik nito ang buong pangalan ng klase. Pangalawa, pinangalanan namin ang aming variable clazz. Medyo kakaiba. Siyempre, dapat itong tawaging "klase", ngunit ang "klase" ay isang nakalaan na salita sa wikang Java, at hindi papayagan ng compiler na tawagin ang mga variable sa ganoong paraan. Kinailangan kong umalis dito :) Well, hindi isang masamang simula! Ano pa ang mayroon tayo sa listahan ng mga posibilidad?

Paano makakuha ng impormasyon tungkol sa mga modifier ng klase, field, pamamaraan, constants, constructor at superclass

Ito ay mas kawili-wili! Sa kasalukuyang klase wala kaming constants at walang parent class. Idagdag natin ang mga ito para sa pagkakumpleto. Gumawa tayo ng pinakasimpleng parent class Animal:
package learn.javarush;
public class Animal {

   private String name;
   private int age;
}
At magdagdag tayo Catng mana mula Animalsa at isang pare-pareho sa ating klase:
package learn.javarush;

public class Cat extends Animal {

   private static final String ANIMAL_FAMILY = "Семейство кошачьих";

   private String name;
   private int age;

   //...остальная часть класса
}
Ngayon ay mayroon na tayong kumpletong set! Subukan natin ang mga posibilidad ng pagmuni-muni :)
import learn.javarush.Cat;

import java.util.Arrays;

public class CodeAnalyzer {

   public static void analyzeClass(Object o) {
       Class clazz = o.getClass();
       System.out.println("Name класса: " + clazz);
       System.out.println("Поля класса: " + Arrays.toString(clazz.getDeclaredFields()));
       System.out.println("Родительский класс: " + clazz.getSuperclass());
       System.out.println("Методы класса: " +  Arrays.toString(clazz.getDeclaredMethods()));
       System.out.println("Конструкторы класса: " + Arrays.toString(clazz.getConstructors()));
   }

   public static void main(String[] args) {

       analyzeClass(new Cat("Barsik", 6));
   }
}
Ito ang nakukuha natin sa console:
Name класса: class learn.javarush.Cat
Поля класса: [private static final java.lang.String learn.javarush.Cat.ANIMAL_FAMILY, private java.lang.String learn.javarush.Cat.name, private int learn.javarush.Cat.age]
Родительский класс: class learn.javarush.Animal
Методы класса: [public java.lang.String learn.javarush.Cat.getName(), public void learn.javarush.Cat.setName(java.lang.String), public void learn.javarush.Cat.sayMeow(), public void learn.javarush.Cat.setAge(int), public void learn.javarush.Cat.jump(), public int learn.javarush.Cat.getAge()]
Конструкторы класса: [public learn.javarush.Cat(java.lang.String,int)]
Nakatanggap kami ng napakaraming detalyadong impormasyon tungkol sa klase! At hindi lamang tungkol sa publiko, kundi pati na rin sa mga pribadong bahagi. Bigyang-pansin: private-Ang mga variable ay ipinapakita din sa listahan. Sa totoo lang, ang "pagsusuri" ng klase ay maaaring ituring na kumpleto sa puntong ito: ngayon, gamit ang pamamaraan, analyzeClass()matututunan natin ang lahat ng posible. Ngunit hindi ito ang lahat ng mga posibilidad na mayroon tayo kapag nagtatrabaho nang may pagmuni-muni. Huwag nating limitahan ang ating sarili sa simpleng pagmamasid at magpatuloy sa aktibong pagkilos! :)

Paano lumikha ng isang halimbawa ng isang klase kung ang pangalan ng klase ay hindi kilala bago ang programa ay naisakatuparan

Magsimula tayo sa default na tagabuo. Wala pa ito sa ating klase Cat, kaya idagdag natin ito:
public Cat() {

}
Narito kung ano ang magiging hitsura ng code upang lumikha ng isang bagay Catgamit ang reflection (method createCat()):
import learn.javarush.Cat;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

public class Main {

   public static Cat createCat() throws IOException, IllegalAccessException, InstantiationException, ClassNotFoundException {

       BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
       String className = reader.readLine();

       Class clazz = Class.forName(className);
       Cat cat = (Cat) clazz.newInstance();

       return cat;
   }

public static Object createObject() throws Exception {

   BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
   String className = reader.readLine();

   Class clazz = Class.forName(className);
   Object result = clazz.newInstance();

   return result;
}

   public static void main(String[] args) throws IOException, IllegalAccessException, ClassNotFoundException, InstantiationException {
       System.out.println(createCat());
   }
}
Pumasok sa console:

learn.javarush.Cat
Output ng console:

Cat{name='null', age=0}
Hindi ito isang error: ang mga halaga nameat ageipinapakita sa console dahil na-program namin ang kanilang output sa paraan ng toString()klase Cat. Dito namin nabasa ang pangalan ng klase na ang object ay gagawin namin mula sa console. Natututo ng tumatakbong programa ang pangalan ng klase kung saan ang bagay na gagawin nito. Mga halimbawa ng paggamit ng Reflection - 3Para sa kapakanan ng kaiklian, inalis namin ang code para sa wastong paghawak ng exception upang hindi ito tumagal ng mas maraming espasyo kaysa sa halimbawa mismo. Sa isang tunay na programa, siyempre, tiyak na sulit ang paghawak ng mga sitwasyon kung saan ang mga maling pangalan ay ipinasok, atbp. Ang default na tagabuo ay isang medyo simpleng bagay, kaya ang paglikha ng isang halimbawa ng isang klase gamit ito, tulad ng nakikita mo, ay hindi mahirap :) At gamit ang pamamaraan, newInstance()lumikha kami ng isang bagong bagay ng klase na ito. Ito ay isa pang bagay kung ang tagabuo ng klase Catay kumukuha ng mga parameter bilang input. Alisin natin ang default na constructor sa klase at subukang patakbuhin muli ang ating code.

null
java.lang.InstantiationException: learn.javarush.Cat
  at java.lang.Class.newInstance(Class.java:427)
Nagkaproblema! Nakatanggap kami ng error dahil tumawag kami ng paraan para gumawa ng object sa pamamagitan ng default na constructor. Ngunit ngayon wala kaming ganoong taga-disenyo. Nangangahulugan ito na kapag gumagana ang pamamaraan, newInstance()gagamitin ng mekanismo ng pagmuni-muni ang aming lumang constructor na may dalawang parameter:
public Cat(String name, int age) {
   this.name = name;
   this.age = age;
}
Ngunit wala kaming ginawa sa mga parameter, na parang nakalimutan namin ang tungkol sa mga ito! Upang maipasa ang mga ito sa constructor gamit ang reflection, kakailanganin mong i-tweak ito ng kaunti:
import learn.javarush.Cat;

import java.lang.reflect.InvocationTargetException;

public class Main {

   public static Cat createCat()  {

       Class clazz = null;
       Cat cat = null;

       try {
           clazz = Class.forName("learn.javarush.Cat");
           Class[] catClassParams = {String.class, int.class};
           cat = (Cat) clazz.getConstructor(catClassParams).newInstance("Barsik", 6);
       } catch (ClassNotFoundException e) {
           e.printStackTrace();
       } catch (InstantiationException e) {
           e.printStackTrace();
       } catch (IllegalAccessException e) {
           e.printStackTrace();
       } catch (NoSuchMethodException e) {
           e.printStackTrace();
       } catch (InvocationTargetException e) {
           e.printStackTrace();
       }

       return cat;
   }

   public static void main(String[] args) {
       System.out.println(createCat());
   }
}
Output ng console:

Cat{name='Barsik', age=6}
Tingnan natin ang mga nangyayari sa ating programa. Nakagawa kami ng isang hanay ng mga bagay Class.
Class[] catClassParams = {String.class, int.class};
Tumutugma ang mga ito sa mga parameter ng aming constructor (mayroon lang kaming mga parameter Stringat int). Ipinapasa namin ang mga ito sa pamamaraan clazz.getConstructor()at makakuha ng access sa kinakailangang constructor. Pagkatapos nito, ang natitira na lang ay tawagan ang pamamaraan newInstance()na may mga kinakailangang parameter at huwag kalimutang tahasang ihagis ang bagay sa klase na kailangan natin - Cat.
cat = (Cat) clazz.getConstructor(catClassParams).newInstance("Barsik", 6);
Bilang resulta, matagumpay na malilikha ang aming bagay! Output ng console:

Cat{name='Barsik', age=6}
Mag move on na tayo :)

Paano makuha at itakda ang halaga ng isang object field ayon sa pangalan

Isipin na gumagamit ka ng isang klase na isinulat ng isa pang programmer. Gayunpaman, wala kang pagkakataong i-edit ito. Halimbawa, isang yari na library ng klase na nakabalot sa isang JAR. Maaari mong basahin ang code ng klase, ngunit hindi mo ito mababago. Ang programmer na lumikha ng klase sa library na ito (hayaan itong ang aming lumang klase Cat) ay hindi nakakuha ng sapat na tulog bago ang huling disenyo at inalis ang mga getter at setter para sa field age. Ngayon ang klase na ito ay dumating sa iyo. Ito ay ganap na nakakatugon sa iyong mga pangangailangan, dahil kailangan mo lang ng mga bagay sa programa Cat. Ngunit kailangan mo sila sa parehong larangan age! Ito ay isang problema: hindi namin maabot ang field, dahil mayroon itong modifier private, at ang mga getter at setter ay inalis ng magiging developer ng klase na ito :/ Well, makakatulong din ang reflection sa sitwasyong ito! CatMayroon kaming access sa class code : maaari naming malaman kung anong mga field ang mayroon ito at kung ano ang tawag sa kanila. Gamit ang impormasyong ito, nilulutas namin ang aming problema:
import learn.javarush.Cat;

import java.lang.reflect.Field;

public class Main {

   public static Cat createCat()  {

       Class clazz = null;
       Cat cat = null;
       try {
           clazz = Class.forName("learn.javarush.Cat");
           cat = (Cat) clazz.newInstance();

           //с полем name нам повезло - для него в классе есть setter
           cat.setName("Barsik");

           Field age = clazz.getDeclaredField("age");

           age.setAccessible(true);

           age.set(cat, 6);

       } catch (IllegalAccessException e) {
           e.printStackTrace();
       } catch (InstantiationException e) {
           e.printStackTrace();
       } catch (ClassNotFoundException e) {
           e.printStackTrace();
       } catch (NoSuchFieldException e) {
           e.printStackTrace();
       }

       return cat;
   }

   public static void main(String[] args) {
       System.out.println(createCat());
   }
}
Tulad ng nakasaad sa komento, nameang lahat ay simple sa field: ang mga developer ng klase ay nagbigay ng setter para dito. Alam mo na rin kung paano lumikha ng mga bagay mula sa mga default na konstruktor: mayroong isang paraan para dito newInstance(). Ngunit kailangan mong mag-ukit sa pangalawang larangan. Alamin natin kung ano ang nangyayari dito :)
Field age = clazz.getDeclaredField("age");
Dito namin, gamit ang aming object Class clazz, i-access ang field agegamit ang method getDeclaredField(). Nagbibigay ito sa amin ng kakayahang makuha ang larangan ng edad bilang isang bagay Field age. Ngunit ito ay hindi pa sapat, dahil privateang mga patlang ay hindi maaaring italaga lamang ng mga halaga. Upang gawin ito, kailangan mong gawing "magagamit" ang field gamit ang pamamaraan setAccessible():
age.setAccessible(true);
Ang mga field kung saan ito ginagawa ay maaaring magtalaga ng mga halaga:
age.set(cat, 6);
Tulad ng nakikita mo, mayroon kaming isang uri ng setter na nakabaligtad: itinatalaga namin ang patlang Field agena halaga nito, at ipinapasa din dito ang bagay kung saan dapat italaga ang patlang na ito. Patakbuhin natin ang aming pamamaraan main()at tingnan:

Cat{name='Barsik', age=6}
Mahusay, ginawa namin ang lahat! :) Tingnan natin kung ano pa ang mga posibilidad na mayroon tayo...

Paano tawagan ang pamamaraan ng isang bagay sa pamamagitan ng pangalan

Bahagyang baguhin natin ang sitwasyon mula sa nakaraang halimbawa. Sabihin nating Catnagkamali ang developer ng klase sa mga patlang - parehong available, may mga getter at setter para sa kanila, ok ang lahat. Iba ang problema: ginawa niyang pribado ang isang paraan na talagang kailangan namin:
private void sayMeow() {

   System.out.println("Meow!");
}
Bilang resulta, gagawa kami ng mga bagay Catsa aming programa, ngunit hindi namin matatawag ang kanilang pamamaraan sayMeow(). Magkakaroon ba tayo ng mga pusang hindi ngumiyaw? Medyo kakaiba :/ Paano ko ito maaayos? Muli, sumagip ang Reflection API! Alam namin ang pangalan ng kinakailangang pamamaraan. Ang natitira ay isang bagay ng pamamaraan:
import learn.javarush.Cat;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class Main {

   public static void invokeSayMeowMethod()  {

       Class clazz = null;
       Cat cat = null;
       try {

           cat = new Cat("Barsik", 6);

           clazz = Class.forName(Cat.class.getName());

           Method sayMeow = clazz.getDeclaredMethod("sayMeow");

           sayMeow.setAccessible(true);

           sayMeow.invoke(cat);

       } catch (ClassNotFoundException e) {
           e.printStackTrace();
       } catch (NoSuchMethodException e) {
           e.printStackTrace();
       } catch (IllegalAccessException e) {
           e.printStackTrace();
       } catch (InvocationTargetException e) {
           e.printStackTrace();
       }
   }

   public static void main(String[] args) {
       invokeSayMeowMethod();
   }
}
Dito kami kumikilos sa halos parehong paraan tulad ng sa sitwasyong may access sa isang pribadong field. Una makuha namin ang pamamaraan na kailangan namin, na naka-encapsulated sa isang object ng klase Method:
Method sayMeow = clazz.getDeclaredMethod("sayMeow");
Sa tulong, getDeclaredMethod()maaari kang "maabot" sa mga pribadong pamamaraan. Susunod na ginagawa namin ang pamamaraan na matatawag:
sayMeow.setAccessible(true);
At sa wakas, tinatawag namin ang pamamaraan sa nais na bagay:
sayMeow.invoke(cat);
Ang pagtawag sa isang pamamaraan ay mukhang isang "pabaliktad na tawag": nakasanayan nating ituro ang isang bagay sa kinakailangang pamamaraan gamit ang isang tuldok ( cat.sayMeow()), at kapag nagtatrabaho sa pagmuni-muni, ipinapasa namin sa pamamaraan ang bagay kung saan kailangan itong tawagan. . Ano ang mayroon tayo sa console?

Meow!
Lahat ay nagtagumpay! :) Ngayon nakita mo na kung ano ang malawak na posibilidad na ibinibigay sa atin ng mekanismo ng pagmuni-muni sa Java. Sa mahirap at hindi inaasahang mga sitwasyon (tulad ng sa mga halimbawa sa isang klase mula sa isang saradong aklatan), ito ay talagang makakatulong sa atin ng malaki. Gayunpaman, tulad ng anumang dakilang kapangyarihan, nagpapahiwatig din ito ng malaking responsibilidad. Ang mga kawalan ng pagmuni-muni ay isinulat tungkol sa isang espesyal na seksyon sa website ng Oracle. Mayroong tatlong pangunahing kawalan:
  1. Bumababa ang pagiging produktibo. Ang mga pamamaraan na tinatawag na gamit ang pagmuni-muni ay may mas mababang pagganap kaysa sa mga pamamaraan na karaniwang tinatawag.

  2. May mga paghihigpit sa kaligtasan. Ang mekanismo ng pagmuni-muni ay nagpapahintulot sa iyo na baguhin ang pag-uugali ng programa sa panahon ng runtime. Ngunit sa iyong kapaligiran sa trabaho sa isang tunay na proyekto ay maaaring may mga paghihigpit na hindi nagpapahintulot sa iyo na gawin ito.

  3. Panganib ng pagsisiwalat ng panloob na impormasyon. Mahalagang maunawaan na ang paggamit ng pagmuni-muni ay direktang lumalabag sa prinsipyo ng encapsulation: pinapayagan kaming ma-access ang mga pribadong field, pamamaraan, atbp. Sa tingin ko ay hindi na kailangang ipaliwanag na ang direkta at matinding paglabag sa mga prinsipyo ng OOP ay dapat gamitin lamang sa pinakamatinding mga kaso, kapag walang ibang mga paraan upang malutas ang problema sa mga kadahilanang hindi mo kontrolado.

Gamitin ang mekanismo ng pagmuni-muni nang matalino at lamang sa mga sitwasyon kung saan hindi ito maiiwasan, at huwag kalimutan ang tungkol sa mga pagkukulang nito. Ito ay nagtatapos sa aming panayam! Ito ay naging medyo malaki, ngunit ngayon ay natutunan mo ang maraming mga bagong bagay :)
Mga komento
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION