CodeGym /Java Blog /Random /Mga halimbawa ng repleksyon
John Squirrels
Antas
San Francisco

Mga halimbawa ng repleksyon

Nai-publish sa grupo
Siguro na-encounter mo na ang konsepto ng "reflection" sa ordinaryong buhay. Ang salitang ito ay karaniwang 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, at maging ang pagbabago ng istraktura at pag-uugali ng isang programa, habang tumatakbo ang programa. Mga halimbawa ng repleksyon - 1 Ang mahalaga dito ay ginagawa namin ito sa runtime, hindi sa oras ng pag-compile. Ngunit bakit suriin ang code sa runtime? Pagkatapos ng lahat, maaari mo nang basahin ang code :/ May dahilan kung bakit maaaring hindi agad malinaw ang ideya ng pagmuni-muni: hanggang sa puntong ito, palagi mong alam kung aling mga klase ang iyong pinagtatrabahuhan. Halimbawa, maaari kang magsulat ng isang Catklase:

package learn.codegym;

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, at makikita mo ang mga field at pamamaraan na mayroon ito. Ipagpalagay na biglang kailangan mong ipakilala ang iba pang mga klase ng hayop sa programa. Malamang na maaari kang lumikha ng isang istraktura ng mana ng klase na may isang Animalklase ng magulang para sa kaginhawahan. Mas maaga, gumawa pa kami ng klase na kumakatawan sa isang beterinaryo na klinika, kung saan maaari kaming magpasa ng isang Animalbagay (halimbawa ng isang klase ng magulang), at ang programa ay tinatrato ang hayop nang naaangkop batay sa kung ito ay isang aso o isang pusa. Kahit na hindi ito ang mga pinakasimpleng gawain, natututuhan ng programa ang lahat ng kinakailangang impormasyon tungkol sa mga klase sa oras ng pag-compile. Alinsunod dito, kapag ipinasa mo ang Catisang bagay sa mga pamamaraan ng klase ng klinika ng beterinaryo samain()paraan, alam na ng programa na ito ay isang pusa, hindi isang aso. Ngayon isipin natin na tayo ay nahaharap sa ibang gawain. Ang aming layunin ay magsulat ng isang code analyzer. Kailangan nating lumikha ng isang CodeAnalyzerklase na may iisang pamamaraan: void analyzeObject(Object o). Ang pamamaraang ito ay dapat:
  • tukuyin ang klase ng bagay na ipinasa dito at ipakita ang pangalan ng klase sa console;
  • tukuyin ang mga pangalan ng lahat ng mga field ng naipasa na klase, kabilang ang mga pribado, at ipakita ang mga ito sa console;
  • tukuyin ang mga pangalan ng lahat ng mga pamamaraan ng naipasa na klase, 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) {
      
       // Print the name of the class of object o
       // Print the names of all variables of this class
       // Print the names of all methods of this class
   }
  
}
Ngayon ay malinaw na nating nakikita kung paano naiiba ang gawaing ito sa iba pang mga gawain na nalutas mo dati. Sa aming kasalukuyang layunin, ang kahirapan ay nakasalalay sa katotohanan na hindi namin alam o ng programa kung ano ang eksaktong ipapasa saanalyzeClass()paraan. Kung magsusulat ka ng ganoong programa, sisimulan itong gamitin ng ibang mga programmer, at maaari nilang ipasa ang anuman sa pamamaraang ito — anumang karaniwang klase ng Java o anumang iba pang klase na kanilang isinusulat. Ang naipasa na klase ay maaaring magkaroon ng anumang bilang ng mga variable at pamamaraan. Sa madaling salita, kami (at ang aming programa) ay walang ideya kung anong mga klase ang aming gagawin. Ngunit gayon pa man, kailangan nating tapusin ang gawaing ito. At dito tumulong ang karaniwang Java Reflection API. Ang Reflection API ay isang makapangyarihang tool ng wika. Inirerekomenda ng opisyal na dokumentasyon ng Oracle na ang mekanismong ito ay dapat lamang gamitin ng mga makaranasang programmer na alam kung ano ang kanilang ginagawa. Malapit mo nang maunawaan kung bakit kami nagbibigay ng ganitong uri ng babala nang maaga :) Narito ang isang listahan ng mga bagay na maaari mong gawin sa Reflection API:
  1. Kilalanin / 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 isang (mga) ipinatupad na interface.
  4. Lumikha ng isang instance ng isang klase na ang pangalan ng klase ay hindi kilala hanggang sa ang programa ay naisakatuparan.
  5. Kunin at itakda ang halaga ng field ng instance ayon sa pangalan.
  6. Tumawag ng paraan ng instance ayon sa pangalan.
Kahanga-hangang listahan, ha? :) Tandaan:magagawa ng mekanismo ng pagmuni-muni ang lahat ng bagay na ito "on the fly", anuman ang uri ng bagay na ipinapasa namin sa aming code analyzer! Tuklasin natin ang mga kakayahan ng Reflection API sa pamamagitan ng pagtingin sa ilang halimbawa.

Paano matukoy/matukoy ang klase ng isang bagay

Magsimula tayo sa mga pangunahing kaalaman. Ang entry point sa Java reflection engine ay ang Classklase. Oo, mukhang nakakatawa talaga, ngunit iyon ang pagmuni-muni :) Gamit ang Classklase, una naming tinutukoy ang klase ng anumang bagay na ipinasa sa aming pamamaraan. Subukan nating gawin ito:

import learn.codegym.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("Fluffy", 6));
   }
}
Output ng console:

class learn.codegym.Cat
Bigyang-pansin ang dalawang bagay. Una, sinasadya naming ilagay ang Catklase sa isang hiwalay learn.codegymna pakete. Ngayon ay makikita mo na ang getClass()pamamaraan ay nagbabalik ng buong pangalan ng klase. Pangalawa, pinangalanan namin ang aming variable clazz. Medyo kakaiba iyon. Makatuwirang tawagin itong "klase", ngunit ang "klase" ay isang nakalaan na salita sa Java. Hindi papayagan ng compiler na matawag iyon ang mga variable. Kinailangan naming libutin iyon kahit papaano :) Not bad for a start! Ano pa ang mayroon tayo sa listahan ng mga kakayahan?

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

Ngayon ang mga bagay ay nagiging mas kawili-wili! Sa kasalukuyang klase, wala kaming anumang constants o parent class. Idagdag natin sila para makagawa ng kumpletong larawan. Lumikha ng pinakasimpleng Animalklase ng magulang:

package learn.codegym;
public class Animal {

   private String name;
   private int age;
}
At gagawin naming Catmagmana ang aming klase Animalat magdagdag ng isang pare-pareho:

package learn.codegym;

public class Cat extends Animal {

   private static final String ANIMAL_FAMILY = "Feline family";

   private String name;
   private int age;

   // ...the rest of the class
}
Ngayon ay mayroon na tayong kumpletong larawan! Tingnan natin kung ano ang kaya ng reflection :)

import learn.codegym.Cat;

import java.util.Arrays;

public class CodeAnalyzer {

   public static void analyzeClass(Object o) {
       Class clazz = o.getClass();
       System.out.println("Class name: " + clazz);
       System.out.println("Class fields: " + Arrays.toString(clazz.getDeclaredFields()));
       System.out.println("Parent class: " + clazz.getSuperclass());
       System.out.println("Class methods: " + Arrays.toString(clazz.getDeclaredMethods()));
       System.out.println("Class constructors: " + Arrays.toString(clazz.getConstructors()));
   }

   public static void main(String[] args) {

       analyzeClass(new Cat("Fluffy", 6));
   }
}
Narito ang nakikita natin sa console:

Class name:  class learn.codegym.Cat 
Class fields: [private static final java.lang.String learn.codegym.Cat.ANIMAL_FAMILY, private java.lang.String learn.codegym.Cat.name, private int learn.codegym.Cat.age] 
Parent class: class learn.codegym.Animal 
Class methods: [public java.lang.String learn.codegym.Cat.getName(), public void learn.codegym.Cat.setName(java.lang.String), public void learn.codegym.Cat.sayMeow(), public void learn.codegym.Cat.setAge(int), public void learn.codegym.Cat.jump(), public int learn.codegym.Cat.getAge()] 
Class constructors: [public learn.codegym.Cat(java.lang.String, int)]
Tingnan ang lahat ng detalyadong impormasyon ng klase na nakuha namin! At hindi lamang pampublikong impormasyon, kundi pati na rin ang pribadong impormasyon! Tandaan: privateang mga variable ay ipinapakita din sa listahan. Ang aming "pagsusuri" ng klase ay maaaring ituring na mahalagang kumpleto: ginagamit namin ang analyzeObject()pamamaraan upang matutunan ang lahat ng aming makakaya. Ngunit hindi ito lahat ng magagawa natin sa pagmumuni-muni. Hindi tayo limitado sa simpleng pagmamasid — magpapatuloy tayo sa pagkilos! :)

Paano lumikha ng isang instance ng isang klase na ang pangalan ng klase ay hindi kilala hanggang sa ang programa ay naisakatuparan.

Magsimula tayo sa default na tagabuo. Ang aming Catklase ay wala pa, kaya't idagdag natin ito:

public Cat() {
  
}
Narito ang code para sa paglikha ng isang Catbagay gamit ang reflection ( createCat()method):

import learn.codegym.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());
   }
}
input ng console:

learn.codegym.Cat
Output ng console:

Cat{name='null', age=0}
Ito ay hindi isang error: ang mga halaga ng nameat ageay ipinapakita sa console dahil sumulat kami ng code upang i-output ang mga ito sa toString()paraan ng Catklase. Dito namin nabasa ang pangalan ng isang klase na ang object ay gagawin namin mula sa console. Kinikilala ng programa ang pangalan ng klase na ang bagay ay gagawin. Mga halimbawa ng repleksyon - 3Para sa kapakanan ng kaiklian, inalis namin ang wastong exception handling code, na kukuha ng mas maraming espasyo kaysa sa halimbawa mismo. Sa isang tunay na programa, siyempre, dapat mong hawakan ang mga sitwasyon na kinasasangkutan ng mga maling naipasok na pangalan, atbp. Ang default na tagabuo ay medyo simple, kaya tulad ng nakikita mo, ito ay madaling gamitin upang lumikha ng isang halimbawa ng klase :) Gamit ang newInstance()pamamaraan , gumawa kami ng bagong object ng klase na ito. Ibang usapin kung angCatconstructor ay tumatagal ng mga argumento bilang input. Alisin natin ang default na constructor ng klase at subukang patakbuhin muli ang ating code.

null
java.lang.InstantiationException: learn.codegym.Cat 
at java.lang.Class.newInstance(Class.java:427)
Nagkaproblema! Nagkaroon kami ng error dahil tumawag kami ng paraan para gumawa ng object gamit ang default constructor. Ngunit wala kaming ganoong constructor ngayon. Kaya kapag newInstance()tumatakbo ang pamamaraan, ginagamit 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! Ang paggamit ng pagmuni-muni sa pagpasa ng mga argumento sa tagabuo ay nangangailangan ng kaunting "pagkamalikhain":

import learn.codegym.Cat;

import java.lang.reflect.InvocationTargetException;

public class Main {

   public static Cat createCat()  {

       Class clazz = null;
       Cat cat = null;

       try {
           clazz = Class.forName("learn.codegym.Cat");
           Class[] catClassParams = {String.class, int.class};
           cat = (Cat) clazz.getConstructor(catClassParams).newInstance("Fluffy", 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='Fluffy', age=6}
Tingnan natin ang mga nangyayari sa ating programa. Gumawa kami ng hanay ng Classmga bagay.

Class[] catClassParams = {String.class, int.class};
Ang mga ito ay tumutugma sa mga parameter ng aming constructor (na mayroon lamang Stringat intmga parameter). Ipinapasa namin ang mga ito sa clazz.getConstructor()pamamaraan at makakuha ng access sa nais na constructor. Pagkatapos nito, ang kailangan lang nating gawin ay tawagan ang newInstance()pamamaraan na may mga kinakailangang argumento, at huwag kalimutang tahasang ihagis ang bagay sa nais na uri: Cat.

cat = (Cat) clazz.getConstructor(catClassParams).newInstance("Fluffy", 6);
Ngayon ang aming object ay matagumpay na nalikha! Output ng console:

Cat{name='Fluffy', age=6}
Gumagalaw kaagad :)

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

Isipin na gumagamit ka ng isang klase na isinulat ng isa pang programmer. Bukod pa rito, wala kang kakayahang i-edit ito. Halimbawa, isang yari na library ng klase na nakabalot sa isang JAR. Maaari mong basahin ang code ng mga klase, ngunit hindi mo ito mababago. Ipagpalagay na ang programmer na lumikha ng isa sa mga klase sa library na ito (hayaan itong maging ang aming lumang Catklase), na hindi nakakuha ng sapat na tulog sa gabi bago natapos ang disenyo, inalis ang getter at setter para sa agefield. Ngayon ang klase na ito ay dumating sa iyo. Natutugunan nito ang lahat ng iyong mga pangangailangan, dahil kailangan mo lang Catng mga bagay sa iyong programa. Ngunit kailangan mo silang magkaroon ng isang agelarangan! Ito ay isang problema: hindi namin maabot ang field, dahil mayroon itongprivatemodifier, at ang getter at setter ay tinanggal ng developer na kulang sa tulog na lumikha ng klase :/ Well, makakatulong sa atin ang pagmuni-muni sa sitwasyong ito! Mayroon kaming access sa code para sa Catklase, upang malaman namin kung anong mga field ang mayroon ito at kung ano ang tawag sa mga ito. Gamit ang impormasyong ito, malulutas namin ang aming problema:

import learn.codegym.Cat;

import java.lang.reflect.Field;

public class Main {

   public static Cat createCat()  {

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

           // We got lucky with the name field, since it has a setter
           cat.setName("Fluffy");

           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 mga komento, lahat ng bagay na may namefield ay diretso, dahil ang mga developer ng klase ay nagbigay ng setter. Alam mo na kung paano lumikha ng mga bagay mula sa mga default na konstruktor: mayroon kaming newInstance()para dito. Ngunit kailangan nating gumawa ng ilang tinkering sa pangalawang larangan. Alamin natin kung ano ang nangyayari dito :)

Field age = clazz.getDeclaredField("age");
Dito, gamit ang aming Class clazzobject , ina-access namin ang agefield sa pamamagitan ng getDeclaredField()pamamaraan. Nagbibigay-daan ito sa amin na makuha ang larangan ng edad bilang isang Field agebagay. Ngunit hindi ito sapat, dahil hindi tayo basta-basta magtalaga ng mga halaga sa privatemga patlang. Upang gawin ito, kailangan nating gawing naa-access ang field sa pamamagitan ng paggamit ng setAccessible()pamamaraan:

age.setAccessible(true);
Kapag nagawa na namin ito sa isang field, maaari kaming magtalaga ng value:

age.set(cat, 6);
Tulad ng nakikita mo, ang aming Field ageobject ay may isang uri ng inside-out setter kung saan ipinapasa namin ang isang int value at ang object kung saan ang field ay itatalaga. Pinapatakbo namin ang aming main()pamamaraan at nakikita:

Cat{name='Fluffy', age=6}
Magaling! Nagawa natin! :) Tingnan natin kung ano pa ang magagawa natin...

Paano tumawag sa isang instance method sa pamamagitan ng pangalan.

Bahagyang baguhin natin ang sitwasyon sa nakaraang halimbawa. Sabihin nating Cathindi nagkamali ang developer ng klase sa mga getter at setter. Okay naman ang lahat sa bagay na iyon. Ngayon ang problema ay iba: mayroong isang paraan na tiyak na kailangan namin, ngunit ginawa itong pribado ng developer:

private void sayMeow() {

   System.out.println("Meow!");
}
Nangangahulugan ito na kung lumikha kami Catng mga bagay sa aming programa, hindi namin matatawagan ang sayMeow()pamamaraan sa kanila. Magkakaroon tayo ng mga pusang hindi ngumiyaw? Kakaiba iyon :/ Paano natin ito aayusin? Muli, tinutulungan tayo ng Reflection API! Alam namin ang pangalan ng pamamaraan na kailangan namin. Ang lahat ng iba pa ay isang teknikalidad:

import learn.codegym.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("Fluffy", 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 ginagawa namin ang halos parehong bagay na ginawa namin noong nag-access sa isang pribadong field. Una, nakukuha namin ang pamamaraan na kailangan namin. Ito ay naka-encapsulated sa isang Methodbagay:

Method sayMeow = clazz.getDeclaredMethod("sayMeow");
getDeclaredMethod()Hinahayaan tayo ng pamamaraan na makarating sa mga pribadong pamamaraan. Susunod, ginagawa naming matatawag ang pamamaraan:

sayMeow.setAccessible(true);
At sa wakas, tinatawag namin ang pamamaraan sa nais na bagay:

sayMeow.invoke(cat);
Dito, ang aming method call ay mukhang isang "callback": nakasanayan na namin ang paggamit ng isang tuldok upang ituro ang isang bagay sa nais na pamamaraan ( ) cat.sayMeow(), ngunit kapag nagtatrabaho sa pagmuni-muni, ipinapasa namin sa pamamaraan ang bagay na gusto naming tawagan. na paraan. Ano ang nasa aming console?

Meow!
Lahat ay gumana! :) Ngayon ay makikita mo na ang malawak na mga posibilidad na ibinibigay sa atin ng mekanismo ng pagmuni-muni ng Java. Sa mahirap at hindi inaasahang mga sitwasyon (gaya ng ating mga halimbawa sa isang klase mula sa isang saradong aklatan), ito ay talagang makakatulong sa atin ng malaki. Ngunit, tulad ng anumang dakilang kapangyarihan, nagdudulot ito ng malaking responsibilidad. Ang mga kawalan ng pagmuni-muni ay inilarawan sa isang espesyal na seksyon sa website ng Oracle. Mayroong tatlong pangunahing kawalan:
  1. Mas malala ang performance. Ang mga pamamaraan na tinatawag na paggamit ng pagmuni-muni ay may mas masahol na pagganap kaysa sa mga pamamaraan na tinatawag sa karaniwang paraan.

  2. May mga paghihigpit sa seguridad. Hinahayaan kami ng mekanismo ng pagmuni-muni na baguhin ang gawi ng isang programa sa runtime. Ngunit sa iyong lugar ng trabaho, kapag nagtatrabaho sa isang tunay na proyekto, maaari kang humarap sa mga limitasyon na hindi pinapayagan ito.

  3. Panganib ng pagkakalantad ng panloob na impormasyon. Mahalagang maunawaan na ang pagmumuni-muni ay isang direktang paglabag sa prinsipyo ng encapsulation: hinahayaan tayo nitong ma-access ang mga pribadong field, pamamaraan, atbp. Sa palagay ko ay hindi ko na kailangang banggitin na ang isang direkta at lantarang paglabag sa mga prinsipyo ng OOP ay dapat gamitin sa pinakamatinding mga kaso lamang, kapag walang ibang mga paraan upang malutas ang isang problema sa mga kadahilanang hindi mo kontrolado.

Gumamit ng pagmuni-muni nang matalino at sa mga sitwasyon lamang na hindi ito maiiwasan, at huwag kalimutan ang tungkol sa mga pagkukulang nito. Dahil dito, natapos na ang ating aralin. Ito ay naging medyo mahaba, ngunit marami kang natutunan ngayon :)
Mga komento
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION