JavaRush /Java блогы /Random-KK /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Егер кенеттен бағдарламаға жануарлардың басқа кластары қажет болса, ыңғайлы болу үшін жалпы класы бар мұрагерлік жүйені құруға болады . Бұрын біз тіпті ата-аналық нысанды өткізуге болатын ветеринарлық клиника класын жасадық Animalжәне бағдарлама жануарды ит немесе мысық екеніне байланысты емдейтін. Бұл тапсырмалар өте қарапайым болмаса да, бағдарлама компиляция уақытында сыныптар туралы барлық қажетті ақпаратты меңгереді. Сондықтан, әдістегі main()нысанды 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 құжаттамасында бұл механизмді не істеп жатқанын өте жақсы түсінетін тәжірибелі бағдарламашылар ғана пайдалану ұсынылады деп айтылған. Неліктен кенеттен алдын ала мұндай ескертулер берілгенін көп ұзамай түсінесіз :) Міне, Reflection API арқылы не істеуге болатынын тізімі:
  1. Объектінің класын табыңыз/анықтаңыз.
  2. Класс модификаторлары, өрістер, әдістер, тұрақтылар, конструкторлар және суперкласстар туралы ақпарат алыңыз.
  3. Орындалған интерфейске/интерфейстерге қандай әдістер тиесілі екенін табыңыз.
  4. Бағдарлама орындалғанға дейін сынып атауы белгісіз болған кезде сынып данасын жасаңыз.
  5. Нысан өрісінің мәнін атау бойынша алыңыз және орнатыңыз.
  6. Объектінің әдісін аты бойынша шақырыңыз.
Әсерлі тізім, иә? :) Назар аударыңыз:Рефлексия механизмі code анализаторымызға қандай класс an objectісін беретінімізге қарамастан, мұның бәрін «ұшып» жасай алады! Мысалдар арқылы Reflection API мүмкіндіктерін қарастырайық.

Объектінің класын қалай табуға/анықтауға болады

Негіздерден бастайық. Java рефлексия механизміне кіру нүктесі болып табылады Class. Иә, бұл шынымен күлкілі көрінеді, бірақ бұл үшін рефлексия қажет :) Класс көмегімен Classбіз ең алдымен әдісімізге өткен кез келген нысанның класын анықтаймыз. Мынаны қолданып көрейік:
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 тіліндегі резервтелген сөз және компилятор айнымалы мәндерді осылай атауға рұқсат бермейді. Мен одан шығуым керек еді :) Жарайды, жаман бастама емес! Мүмкіндіктер тізімінде бізде тағы не болды?

Класс модификаторлары, өрістер, әдістер, тұрақтылар, конструкторлар және суперкласстар туралы ақпаратты қалай алуға болады

Бұл қазірдің өзінде қызықтырақ! Ағымдағы сыныпта бізде тұрақтылар және ата-аналар класы жоқ. Толық болу үшін оларды қосамыз. Ең қарапайым ата-аналық сыныпты жасайық 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Рефлексия (әдіс) арқылы нысанды жасау үшін 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)
Бірдеңе дұрыс болмады! Біз әдепкі конструктор арқылы нысанды жасау әдісін шақырғандықтан қате алдық. Бірақ қазір бізде ондай дизайнер жоқ. Бұл әдіс жұмыс істегенде, 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}
Бағдарламамызда не болып жатқанын егжей-тегжейлі қарастырайық. Біз нысандар массивін жасадық Class.
Class[] catClassParams = {String.class, int.class};
Олар конструкторымыздың параметрлеріне сәйкес келеді (бізде тек Stringжәне параметрлері ғана бар int). Біз оларды әдіске береміз clazz.getConstructor()және қажетті конструкторға қол жеткіземіз. Осыдан кейін қажетті параметрлері бар әдісті шақыру ғана қалады newInstance()және an objectіні бізге қажет классқа нақты шығаруды ұмытпаңыз - Cat.
cat = (Cat) clazz.getConstructor(catClassParams).newInstance("Barsik", 6);
Нәтижесінде біздің нысан сәтті құрылады! Консоль шығысы:

Cat{name='Barsik', age=6}
Әрі қарай жүрейік :)

Нысан өрісінің мәнін атау бойынша қалай алуға және орнатуға болады

Сіз басқа бағдарламашы жазған сыныпты пайдаланып жатырсыз деп елестетіңіз. Дегенмен, оны өңдеуге мүмкіндігіңіз жоқ. Мысалы, JAR пакетіне салынған дайын сынып кітапханасы. Сіз сынып codeын оқи аласыз, бірақ оны өзгерте алмайсыз. Осы кітапханада сыныпты жасаған программист (бұл біздің ескі сынып болсын Cat) соңғы дизайн алдында жеткілікті ұйықтамады және өріс үшін гетерлер мен орнатушыларды алып тастады age. Енді сендерге бұл сынып келді. Ол сіздің қажеттіліктеріңізді толығымен қанағаттандырады, өйткені сізге тек бағдарламадағы нысандар қажет 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өрісте бәрі қарапайым: сынып әзірлеушілері оған орнатушыны ұсынды. Сіз сондай-ақ әдепкі конструкторлардан нысандарды қалай жасау керектігін білесіз: бұл үшін әдіс бар newInstance(). Бірақ сіз екінші өріспен айналысуыңыз керек. Мұнда не болып жатқанын анықтайық :)
Field age = clazz.getDeclaredField("age");
Мұнда біз өз нысанымызды пайдалана отырып , Class clazzөріске . Ол бізге an object ретінде жас өрісін алуға мүмкіндік береді . Бірақ бұл әлі жеткіліксіз, өйткені өрістерге жай ғана мәндерді тағайындау мүмкін емес. Бұл әрекетті орындау үшін жолды келесі әдіс арқылы «қол жетімді» ету керек : agegetDeclaredField()Field ageprivatesetAccessible()
age.setAccessible(true);
Бұл орындалатын өрістерге мәндер тағайындалуы мүмкін:
age.set(cat, 6);
Көріп отырғаныңыздай, бізде төңкерілген орнатушының түрі бар: біз өріске Field ageоның мәнін береміз, сонымен қатар оған осы өріс тағайындалуы керек нысанды береміз. Әдісімізді іске қосып main(), көрейік:

Cat{name='Barsik', age=6}
Керемет, біз бәрін жасадық! :) Тағы қандай мүмкіндіктер бар екенін көрейік...

Объектінің әдісін аты бойынша қалай атауға болады

Алдыңғы мысалдағы жағдайды сәл өзгертейік. CatКласс әзірлеушісі өрістермен қателесті делік - екеуі де қол жетімді, олар үшін қабылдаушылар мен орнатушылар бар, бәрі жақсы. Мәселе басқа: ол бізге міндетті түрде қажет әдісті жеке жасады:
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();
   }
}
Мұнда біз жеке өріске қол жеткізу жағдайындағыдай әрекет етеміз. Алдымен сынып нысанында инкапсуляцияланған қажетті әдісті аламыз Method:
Method sayMeow = clazz.getDeclaredMethod("sayMeow");
Көмекпен getDeclaredMethod()сіз жеке әдістерге «қол жеткізе аласыз». Әрі қарай әдісті шақыруға болатын етеміз:
sayMeow.setAccessible(true);
Соңында біз қалаған нысандағы әдісті шақырамыз:
sayMeow.invoke(cat);
Әдісті шақыру да «кері шақыруға» ұқсайды: біз нүктені ( ) пайдаланып an objectіні қажетті әдіске көрсетуге дағдыланамыз cat.sayMeow(), ал шағылысумен жұмыс істегенде әдіске оны шақыру керек нысанды береміз. . Консольде не бар?

Meow!
Барлығы орындалды! :) Енді Java тіліндегі рефлексия механизмі бізге қандай кең мүмкіндіктер беретінін көрдіңіз. Күрделі және күтпеген жағдайларда (жабық кітапханадағы сыныптағы мысалдардағы сияқты) ол бізге көп көмектесе алады. Дегенмен, кез келген ұлы держава сияқты ол да үлкен жауапкершілікті білдіреді. Рефлексияның кемшіліктері туралы Oracle веб-сайтындағы арнайы бөлімде жазылған. Үш негізгі кемшілігі бар:
  1. Өнімділік төмендейді. Рефлексия арқылы шақырылатын әдістер қалыпты шақырылатын әдістерге қарағанда өнімділігі төмен.

  2. Қауіпсіздік шектеулері бар. Рефлексия механизмі орындалу уақытында бағдарламаның әрекетін өзгертуге мүмкіндік береді. Бірақ нақты жобадағы жұмыс ортаңызда мұны істеуге мүмкіндік бермейтін шектеулер болуы мүмкін.

  3. Ішкі ақпаратты ашу қаупі. Рефлексияны пайдалану инкапсуляция принципін тікелей бұзатынын түсіну маңызды: ол бізге жеке өрістерге, әдістерге және т.б. Менің ойымша, OOP принциптерін тікелей және өрескел бұзуға тек ең төтенше жағдайларда, сіздің бақылауыңыздан тыс себептер бойынша мәселені шешудің басқа жолдары болмаған кезде ғана жүгіну керек екенін түсіндірудің қажеті жоқ деп ойлаймын.

Рефлексия механизмін ақылмен және оны болдырмау мүмкін емес жағдайларда ғана пайдаланыңыз және оның кемшіліктері туралы ұмытпаңыз. Осымен лекциямыз аяқталды! Бұл өте үлкен болды, бірақ бүгін сіз көптеген жаңа нәрселерді білдіңіз :)
Пікірлер
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION