JavaRush /Java блогу /Random-KY /Javaдагы чагылдыруу - Колдонуу мисалдары

Javaдагы чагылдыруу - Колдонуу мисалдары

Группада жарыяланган
Сиз күнүмдүк жашоодо "чагылышуу" түшүнүгүн кезиктирсеңиз керек. Көбүнчө бул сөз адамдын өзүн изилдөө процессин билдирет. Программалоодо ал окшош мааниге ээ - бул программа жөнүндө маалыматтарды текшерүү механизми, ошондой эле аны аткаруу учурунда программанын структурасын жана жүрүм-турумун өзгөртүү. Бул жерде маанилүү нерсе, ал компиляция убагында эмес, иштөө убагында жасалат. Бирок эмне үчүн codeду иштөө убагында текшериш керек? Сиз буга чейин эле көрүп жатасыз :/ Рефлексияны колдонуу мисалдары - 1Ой жүгүртүү идеясы бир себеп менен дароо ачык-айкын болбошу мүмкүн: ушул убакка чейин сиз иштеп жаткан класстарды ар дайым билдиңиз. Мисалы, сиз класс жаза аласыз 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 +
           '}';
}

}
Сиз бул жөнүндө баарын билесиз, анын кандай тармактары жана ыкмалары бар экенин көрөсүз. Ыңгайлуулук үчүн, албетте, сиз жалпы класс менен мурас тутумун түзө аласыз Animal, эгер күтүлбөгөн жерден программа жаныбарлардын башка класстарын талап кылса. Мурда биз ветеринардык клиниканын классын түздүк, анда сиз ата-энелик an objectти өткөрүп бере аласыз Animalжана программа жаныбарды ит же мышык экенине жараша дарылайт. Бул тапшырмалар өтө жөнөкөй болбосо да, программа компиляция учурунда класстар тууралуу керектүү маалыматтардын баарын үйрөнөт. Ошондуктан, сиз ветеринардык клиника классынын методдоруна main()an objectти өткөрүп бергенде Cat, программа бул ит эмес, мышык экенин билет. Эми алдыбызда дагы бир милдет турат деп элестетип көрөлү. Биздин максат - code анализаторун жазуу. CodeAnalyzerБиз бир метод менен класс түзүшүбүз керек - void analyzeClass(Object o). Бул ыкма керек:
  • an object кайсы класска өткөнүн аныктоо жана класстын атын консолдо көрсөтүү;
  • бул класстын бардык талааларынын аталыштарын, анын ичинде жеке талааларды аныктоо жана аларды консолдо көрсөтүү;
  • бул класстын бардык ыкмаларынын, анын ичинде жеке ыкмаларынын атын аныктап, аларды консолдо көрсөт.
Ал төмөнкүдөй көрүнөт:
public class CodeAnalyzer {

   public static void analyzeClass(Object o) {

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

}
Эми бул көйгөй менен сиз чечкен калган көйгөйлөрдүн ортосундагы айырма көрүнүп турат. Бул учурда, кыйынчылык, сиз да, программа да методго эмне так өтөрүн билбейт analyzeClass(). Сиз программа жазасыз, аны башка программисттер колдоно башташат, алар бул методго каалаган нерсени өткөрүп бере алышат - каалаган стандарттуу Java классы же алар жазган класс. Бул класста каалаган сандагы өзгөрмөлөр жана ыкмалар болушу мүмкүн. Башкача айтканда, бул учурда биз (жана биздин программабыз) кандай класстар менен иштей турганыбызды билбейбиз. Бирок, биз бул маселени чечишибиз керек. Бул жерде стандарттуу Java китепканасы жардамга келет - Java Reflection API. Reflection API күчтүү тил өзгөчөлүгү болуп саналат. Расмий Oracle documentтеринде бул механизм эмне кылып жатканын абдан жакшы түшүнгөн тажрыйбалуу программисттер тарабынан гана колдонуу сунушталат деп айтылат. Эмне үчүн күтүлбөгөн жерден алдын ала мындай эскертүүлөр берилгенин жакында түшүнөсүз :) Бул жерде Reflection API аркылуу эмне кылса болорун тизмеси:
  1. Объекттин классын табуу/аныктоо.
  2. Класстын модификаторлору, талаалары, ыкмалары, константалары, конструкторлору жана суперкласстары жөнүндө маалымат алыңыз.
  3. Ишке ашырылган интерфейске/интерфейстерге кайсы ыкмалар таандык экенин бorп алыңыз.
  4. Программа аткарылмайынча класстын аты белгисиз болгондо класстын үлгүсүн түзүңүз.
  5. Объект талаасынын маанисин аты боюнча алуу жана коюу.
  6. Объекттин ыкмасын аты менен чакырыңыз.
Таасирдүү тизме, ээ? :) Көңүл буруу:Кайсы класс an objectисин биздин code анализаторубузга өткөрүп бергенибизге карабастан, чагылдыруу механизми мунун баарын "учуп баратып" жасай алат! Келгиле, мисалдар менен Reflection API мүмкүнчүлүктөрүн карап көрөлү.

Объекттин классын кантип табууга / аныктоого болот

Негизгилерден баштайлы. Java чагылдыруу механизмине кирүү чекити болуп саналат Class. Ооба, чынында эле күлкүлүү көрүнөт, бирок бул үчүн чагылдыруу :) Классты колдонуп Class, биз биринчи кезекте биздин методубузга өткөн каалаган an objectинин классын аныктайбыз. Келгиле, муну аракет кылып көрөлү:
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));
   }
}
Консолдук чыгаруу:

class learn.javarush.Cat
Эки нерсеге көңүл буруңуз. Биринчиден, классты атайылап Catөзүнчө пакетке салабыз.Эми ал класстын толук атын кайтарып жатканын learn.javarush;көрө аласыз . getClass()Экинчиден, биз өзгөрмөбүздү атадык clazz. Бир аз кызыктай көрүнөт. Албетте, аны "класс" деп аташ керек, бирок "класс" Java тorнде сакталган сөз жана компилятор өзгөрмөлөрдү ушундайча атоого жол бербейт. Мен андан чыгышым керек болчу :) Ооба, жаман башталыш эмес! Мүмкүнчүлүктөрдүн тизмесинде дагы эмнелер бар эле?

Класстын модификаторлору, талаалар, методдор, константалар, конструкторлор жана суперкласстар жөнүндө кантип маалымат алса болот

Бул мурунтан эле кызыктуураак! Азыркы класста бизде туруктуулар жана ата-энелер классы жок. Толук болуу үчүн аларды кошолу. Эң жөнөкөй ата-эне классын түзөлү Animal:
package learn.javarush;
public class Animal {

   private String name;
   private int age;
}
Жана классыбызга Catмурасты жана бир туруктууну кошолу :Animal
package learn.javarush;

public class Cat extends Animal {

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

   private String name;
   private int age;

   //...остальная часть класса
}
Азыр бизде толук комплект бар! Келгиле, ой жүгүртүү мүмкүнчүлүктөрүн сынап көрөлү :)
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));
   }
}
Бул консолдо эмнени алабыз:
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)]
Класс тууралуу абдан кеңири маалымат алдык! Ал эми коомдук жөнүндө гана эмес, ошондой эле жеке бөлүктөрү жөнүндө. Көңүл буруу: private-өзгөрмөлөр тизмеде да көрсөтүлөт. Чынында, класстын "талдоосун" ушул учурда толук деп эсептесе болот: азыр, методду колдонуу менен, analyzeClass()биз мүмкүн болгон нерселердин бардыгын үйрөнөбүз. Бирок булар рефлексия менен иштөөдө бизде болгон бардык мүмкүнчүлүктөр эмес. Жөнөкөй байкоо жүргүзүү менен чектелбей, активдүү аракетке өтөлү! :)

Программа аткарылганга чейин класстын аты белгисиз болсо, класстын үлгүсүн кантип түзүүгө болот

Демейки конструктордон баштайлы. Ал азырынча биздин класста эмес Cat, андыктан аны кошобуз:
public Cat() {

}
CatБул жерде чагылдыруу (метод) аркылуу an objectти түзүү үчүн code кандай болот 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());
   }
}
Консолго кириңиз:

learn.javarush.Cat
Консолдук чыгаруу:

Cat{name='null', age=0}
Бул ката эмес: баалуулуктар nameжана ageконсолдо көрсөтүлөт, анткени биз toString()класстык методдо алардын чыгышын программалаганбыз Cat. Бул жерде биз консолдон an objectин түзө турган класстын атын окуйбуз. Иштеп жаткан программа an objectин түзө турган класстын атын үйрөнөт. Рефлексияны колдонуу мисалдары - 3Кыскараак болуу үчүн, биз мисалдын өзүнөн көбүрөөк орунду ээлебеши үчүн, өзгөчө кырдаалдарды туура иштетүү үчүн codeду өткөрүп жибердик. Чыныгы программада, албетте, туура эмес аттар киргизилген жагдайларды чечүү керек. Демейки конструктор - бул өтө жөнөкөй нерсе, андыктан класстын инстанциясын түзүү, сиз көрүп тургандай, кыйын эмес :) Жана методду колдонуп, newInstance()биз бул класстын жаңы an objectин түзөбүз. CatКласстын конструктору параметрлерди киргизүү катары кабыл алса, бул башка маселе . Келгиле, класстан демейки конструкторду алып салып, codeубузду кайра иштетүүгө аракет кылалы.

null
java.lang.InstantiationException: learn.javarush.Cat
  at java.lang.Class.newInstance(Class.java:427)
Бир жерден ката кетти! Демейки конструктор аркылуу an objectти түзүү ыкмасын чакыргандыктан ката алдык. Бирок азыр бизде андай дизайнер жок. Бул ыкма иштегенде, newInstance()чагылдыруу механизми биздин эски конструкторду эки параметр менен колдонот дегенди билдирет:
public Cat(String name, int age) {
   this.name = name;
   this.age = age;
}
Бирок биз параметрлер менен эч нерсе кылган жокпуз, алар жөнүндө таптакыр унутуп калгандай! Аларды рефлексия аркылуу конструкторго өткөрүү үчүн, сиз аны бир аз чыңдооңуз керек болот:
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());
   }
}
Консолдук чыгаруу:

Cat{name='Barsik', age=6}
Келгиле, программабызда эмне болуп жатканын кененирээк карап көрөлү. Биз an objectтердин массивдерин түздүк Class.
Class[] catClassParams = {String.class, int.class};
Алар биздин конструктордун параметрлерине туура келет (бизде жөн гана Stringжана параметрлери бар int). Биз аларды методго өткөрүп clazz.getConstructor(), керектүү конструкторго мүмкүнчүлүк алабыз. Андан кийин, керектүү параметрлер менен методду чакыруу гана калды newInstance()жана an objectти бизге керектүү класска ачык чыгарууну унутпаңыз - Cat.
cat = (Cat) clazz.getConstructor(catClassParams).newInstance("Barsik", 6);
Натыйжада биздин an object ийгorктүү түзүлөт! Консолдук чыгаруу:

Cat{name='Barsik', age=6}
уланталы :)

Объект талаасынын маанисин аты менен кантип алуу жана орнотуу керек

Башка программист жазган классты колдонуп жатканыңызды элестетиңиз. Бирок, аны түзөтүүгө мүмкүнчүлүгүңүз жок. Мисалы, JARга пакеттелген даяр класс китепканасы. Сиз класстын codeун окуй аласыз, бирок аны өзгөртө албайсыз. Бул китепканада классты түзгөн программист (бул биздин эски класс болсун Cat) акыркы дизайнга чейин жетишерлик уктаган жок жана талаа үчүн алгычтарды жана орнотуучуларды алып салды age. Эми бул класс сизге келди. Бул сиздин муктаждыктарыңызды толугу менен канааттандырат, анткени сизге программадагы an objectтер гана керек Cat. Бирок сизге ошол эле талаа керек age! Бул көйгөй: биз талаага жете албайбыз, анткени анын модификатору бар private, ал эми алуучулар менен орнотуучуларды ушул класстын болочок иштеп чыгуучусу алып салган:/ Ооба, рефлексия бизге бул кырдаалда да жардам берет! CatБизде класстын codeуна кирүү мүмкүнчүлүгү бар: биз жок дегенде анын кандай талаалары бар экенин жана алар эмне деп аталарын биле алабыз. Бул маалымат менен куралданып, биз көйгөйүбүздү чечебиз:
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());
   }
}
Комментарийде айтылгандай, nameталаада бардыгы жөнөкөй: класстын иштеп чыгуучулары ал үчүн орнотуучу менен камсыз кылышкан. Сиз ошондой эле демейки конструкторлордон an objectтерди кантип түзүүнү билесиз: бул үчүн ыкма бар newInstance(). Бирок экинчи талаа менен иштешүүгө туура келет. Келгиле, бул жерде эмне болуп жатканын билели :)
Field age = clazz.getDeclaredField("age");
Бул жерде биз an objectибизди колдонуп Class clazz, талаага age. getDeclaredField()Бул бизге an object катары жаштык талааны алуу мүмкүнчүлүгүн берет Field age. Бирок бул азырынча жетишсиз, анткени privateталааларга жөн эле маанилерди ыйгаруу мүмкүн эмес. Бул үчүн, ыкманы колдонуу менен талааны "жеткorктүү" кылуу керек setAccessible():
age.setAccessible(true);
Бул аткарылган талааларга баалуулуктар берorши мүмкүн:
age.set(cat, 6);
Көрүнүп тургандай, бизде тескери бурулган орнотуучу бар: биз талаага Field ageанын маанисин ыйгарабыз, ошондой эле ага бул талаа дайындалышы керек болгон an objectти өткөрүп беребиз. Келгиле, биздин ыкманы иштетип main()көрөлү:

Cat{name='Barsik', age=6}
Сонун, биз баарын жасадык! :) Келгиле, дагы кандай мүмкүнчүлүктөрүбүз бар экенин карап көрөлү...

Объекттин ыкмасын аты менен кантип чакырса болот

Келгиле, мурунку мисалдан кырдаалды бир аз өзгөртөлү. CatКласстын иштеп чыгуучусу талаалар менен ката кетирди дейли - экөө тең жеткorктүү, алар үчүн алуучулар жана орнотуучулар бар, баары жакшы. Маселе башкада: ал бизге сөзсүз керек болгон ыкманы жеке кылды:
private void sayMeow() {

   System.out.println("Meow!");
}
Натыйжада, биз Catпрограммабызда an objectтерди түзөбүз, бирок алардын ыкмасын чакыра албайбыз sayMeow(). Бизде мияулбаган мышыктар болобу? Абдан кызык :/ Муну кантип оңдой алам? Дагы бир жолу, Reflection API жардамга келет! Биз талап кылынган ыкманын атын билебиз. Калганы техника маселеси:
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();
   }
}
Бул жерде биз жеке талаага жетүү кырдаалындай эле иш кылабыз. Адегенде класс an objectисинде камтылган керектүү методду алабыз Method:
Method sayMeow = clazz.getDeclaredMethod("sayMeow");
Жардамы менен getDeclaredMethod()сиз жеке ыкмаларга "жеткиле" аласыз. Андан кийин биз ыкманы чакыра турган кылабыз:
sayMeow.setAccessible(true);
Акыр-аягы, биз каалаган an object боюнча ыкманы чакырабыз:
sayMeow.invoke(cat);
Методду чакыруу да “тескери чалуу” сыяктуу көрүнөт: биз an objectти талап кылынган ыкмага чекиттин ( cat.sayMeow()) жардамы менен көрсөтүүгө көнүп калганбыз, ал эми чагылдыруу менен иштөөдө биз аны чакыра турган an objectти методго өткөрөбүз. . Консолдо эмнебиз бар?

Meow!
Баары ойдогудай болду! :) Эми сиз Javaдагы чагылдыруу механизми бизге кандай кеңири мүмкүнчүлүктөрдү берип жатканын көрүп жатасыз. Кыйын жана күтүүсүз кырдаалдарда (жабык китепканадан алынган класстардагы мисалдардагыдай) ал бизге чындап эле көп жардам бере алат. Бирок, ар кандай улуу державалар сыяктуу эле бул да чоң жоопкерчorкти билдирет. Ой жүгүртүүнүн кемчorктери жөнүндө Oracle веб-сайтындагы атайын бөлүмдө жазылган . үч негизги кемчorктери бар:
  1. Өндүрүмдүүлүк төмөндөйт. Рефлексияны колдонуу менен деп аталган методдор адаттагыдай деп аталган методдорго караганда төмөн көрсөткүчкө ээ.

  2. Коопсуздук боюнча чектөөлөр бар. Рефлексия механизми иштөө учурунда программанын жүрүм-турумун өзгөртүүгө мүмкүндүк берет. Бирок чыныгы долбоор боюнча сиздин иш чөйрөңүздө муну кылууга жол бербеген чектөөлөр болушу мүмкүн.

  3. Ички маалыматтын ачыкка чыгуу коркунучу. Ой жүгүртүүнү колдонуу түздөн-түз инкапсуляция принцибине каршы келерин түшүнүү керек: ал бизге жеке талааларга, методдорго ж.б. ООПтун принциптерин тикелей жана одоно бузууга сиздерден көз каранды болбогон себептер боюнча маселени чечүүнүн башка жолдору жок болгондо гана эң экстремалдуу учурларда кайрылуу керек деп түшүндүрүүнүн кереги жок деп ойлойм.

Ой жүгүртүү механизмин акылдуулук менен жана андан качуу мүмкүн болбогон учурларда гана колдонуңуз жана анын кемчorктерин унутпаңыз. Бул биздин лекциябызды аяктайт! Бул абдан чоң болуп чыкты, бирок бүгүн сиз көп жаңы нерселерди үйрөндүңүз :)
Комментарийлер
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION