
Cat
klase:
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 Animal
klase 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 Animal
bagay (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 Cat
isang 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 CodeAnalyzer
klase 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.
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:
- Kilalanin / tukuyin ang klase ng isang bagay.
- Kumuha ng impormasyon tungkol sa mga modifier ng klase, field, pamamaraan, constant, constructor, at superclass.
- Alamin kung aling mga pamamaraan ang nabibilang sa isang (mga) ipinatupad na interface.
- Lumikha ng isang instance ng isang klase na ang pangalan ng klase ay hindi kilala hanggang sa ang programa ay naisakatuparan.
- Kunin at itakda ang halaga ng field ng instance ayon sa pangalan.
- Tumawag ng paraan ng instance ayon sa pangalan.
Paano matukoy/matukoy ang klase ng isang bagay
Magsimula tayo sa mga pangunahing kaalaman. Ang entry point sa Java reflection engine ay angClass
klase. Oo, mukhang nakakatawa talaga, ngunit iyon ang pagmuni-muni :) Gamit ang Class
klase, 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 Cat
klase sa isang hiwalay learn.codegym
na 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 pinakasimplengAnimal
klase ng magulang:
package learn.codegym;
public class Animal {
private String name;
private int age;
}
At gagawin naming Cat
magmana ang aming klase Animal
at 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: private
ang 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 amingCat
klase ay wala pa, kaya't idagdag natin ito:
public Cat() {
}
Narito ang code para sa paglikha ng isang Cat
bagay 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 name
at age
ay ipinapakita sa console dahil sumulat kami ng code upang i-output ang mga ito sa toString()
paraan ng Cat
klase. 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. 
newInstance()
pamamaraan , gumawa kami ng bagong object ng klase na ito. Ibang usapin kung angCat
constructor 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 Class
mga bagay.
Class[] catClassParams = {String.class, int.class};
Ang mga ito ay tumutugma sa mga parameter ng aming constructor (na mayroon lamang String
at int
mga 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 lumangCat
klase), na hindi nakakuha ng sapat na tulog sa gabi bago natapos ang disenyo, inalis ang getter at setter para sa age
field. Ngayon ang klase na ito ay dumating sa iyo. Natutugunan nito ang lahat ng iyong mga pangangailangan, dahil kailangan mo lang Cat
ng mga bagay sa iyong programa. Ngunit kailangan mo silang magkaroon ng isang age
larangan! Ito ay isang problema: hindi namin maabot ang field, dahil mayroon itongprivate
modifier, 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 Cat
klase, 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 name
field 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 clazz
object , ina-access namin ang age
field sa pamamagitan ng getDeclaredField()
pamamaraan. Nagbibigay-daan ito sa amin na makuha ang larangan ng edad bilang isang Field age
bagay. Ngunit hindi ito sapat, dahil hindi tayo basta-basta magtalaga ng mga halaga sa private
mga 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 age
object 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 natingCat
hindi 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 Cat
ng 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 Method
bagay:
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:
-
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.
-
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.
-
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.
GO TO FULL VERSION