JavaRush /จาวาบล็อก /Random-TH /การสะท้อนกลับใน Java - ตัวอย่างการใช้งาน

การสะท้อนกลับใน Java - ตัวอย่างการใช้งาน

เผยแพร่ในกลุ่ม
คุณอาจเคยเจอแนวคิดเรื่อง “การสะท้อน” ในชีวิตประจำวัน โดยปกติคำนี้หมายถึงกระบวนการศึกษาตนเอง ในการเขียนโปรแกรมมีความหมายคล้ายกัน - เป็นกลไกในการตรวจสอบข้อมูลเกี่ยวกับโปรแกรมตลอดจนการเปลี่ยนแปลงโครงสร้างและพฤติกรรมของโปรแกรมระหว่างการดำเนินการ สิ่งสำคัญที่นี่คือการดำเนินการที่รันไทม์ ไม่ใช่ในเวลาคอมไพล์ แต่ทำไมต้องตรวจสอบโค้ดตอนรันไทม์? คุณเห็นแล้ว :/ ตัวอย่างการใช้การสะท้อน - 1แนวคิดเรื่องการไตร่ตรองอาจไม่ชัดเจนในทันทีด้วยเหตุผลเดียว: จนถึงขณะนี้ คุณจะรู้อยู่เสมอว่าคุณกำลังเรียนชั้นเรียนอยู่ ตัวอย่างเช่น คุณสามารถเขียน class 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ไปยังวิธีการของชั้นเรียนคลินิกสัตวแพทย์ โปรแกรมจะรู้อยู่แล้วว่านี่คือแมวไม่ใช่สุนัข ทีนี้ลองจินตนาการว่าเรากำลังเผชิญกับงานอื่น เป้าหมายของเราคือการเขียนตัววิเคราะห์โค้ด เราจำเป็นต้องสร้างคลาสCodeAnalyzerด้วยวิธีเดียว - void analyzeClass(Object o). วิธีนี้ควร:
  • กำหนดคลาสที่วัตถุถูกส่งผ่านไปและแสดงชื่อคลาสในคอนโซล
  • กำหนดชื่อของฟิลด์ทั้งหมดของคลาสนี้ รวมถึงฟิลด์ส่วนตัว และแสดงในคอนโซล
  • กำหนดชื่อของเมธอดทั้งหมดของคลาสนี้ รวมถึงเมธอดส่วนตัว และแสดงไว้ในคอนโซล
มันจะมีลักษณะดังนี้:
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. เรียกวิธีการของวัตถุตามชื่อ
รายการที่น่าประทับใจใช่มั้ย? :) ใส่ใจ:กลไกการสะท้อนสามารถทำทั้งหมดนี้ “ได้ทันที” โดยไม่คำนึงว่าคลาสอ็อบเจ็กต์ใดที่เราส่งไปยังเครื่องมือวิเคราะห์โค้ดของเรา! มาดูความสามารถของ Reflection API พร้อมตัวอย่างกัน

วิธีค้นหา / กำหนดคลาสของวัตถุ

เริ่มจากพื้นฐานกันก่อน จุดเริ่มต้นของกลไกการสะท้อนของ Java คือClass. ใช่ มันดูตลกจริงๆ แต่นั่นคือสิ่งที่สะท้อนออกมา :) เมื่อใช้ 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โดยใช้การสะท้อน (วิธีการ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ที่นี่เราอ่านชื่อของคลาสที่เราจะสร้างวัตถุจากคอนโซล โปรแกรมที่รันอยู่จะเรียนรู้ชื่อของคลาสที่วัตถุที่จะสร้าง ตัวอย่างการใช้การสะท้อน - 3เพื่อความกระชับ เราได้ละเว้นโค้ดสำหรับการจัดการข้อยกเว้นที่เหมาะสม เพื่อไม่ให้ใช้พื้นที่มากกว่าตัวอย่าง ในโปรแกรมจริง แน่นอนว่ามันคุ้มค่าที่จะจัดการกับสถานการณ์ที่มีการป้อนชื่อไม่ถูกต้อง ฯลฯ คอนสตรัคเตอร์เริ่มต้นนั้นค่อนข้างง่าย ดังนั้นอย่างที่คุณเห็นว่าการสร้างอินสแตนซ์ของคลาสที่ใช้งานมันนั้นไม่ใช่เรื่องยาก :) และเมื่อใช้วิธีนี้newInstance()เราจะสร้างอ็อบเจ็กต์ใหม่ของคลาสนี้ เป็นอีกเรื่องหนึ่งถ้าตัวสร้างคลาสCatรับพารามิเตอร์เป็นอินพุต ลองลบ Constructor เริ่มต้นออกจากคลาสแล้วลองรันโค้ดของเราอีกครั้ง

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()ด้วยพารามิเตอร์ที่จำเป็นและอย่าลืมส่งวัตถุไปยังคลาสที่เราต้องการอย่างชัดเจนCat-
cat = (Cat) clazz.getConstructor(catClassParams).newInstance("Barsik", 6);
เป็นผลให้วัตถุของเราถูกสร้างขึ้นได้สำเร็จ! เอาต์พุตคอนโซล:

Cat{name='Barsik', age=6}
เดินหน้าต่อไป :)

วิธีรับและตั้งค่าของฟิลด์วัตถุตามชื่อ

ลองนึกภาพว่าคุณกำลังใช้คลาสที่เขียนโดยโปรแกรมเมอร์คนอื่น อย่างไรก็ตาม คุณไม่มีโอกาสที่จะแก้ไขมัน ตัวอย่างเช่น ไลบรารีคลาสสำเร็จรูปที่บรรจุอยู่ใน JAR คุณสามารถอ่านรหัสชั้นเรียนได้ แต่จะเปลี่ยนแปลงไม่ได้ โปรแกรมเมอร์ที่สร้างคลาสในไลบรารีนี้ (ปล่อยให้เป็นคลาสเก่าของเราCat) นอนหลับไม่เพียงพอก่อนการออกแบบขั้นสุดท้าย และลบ getters และ setters สำหรับฟิลด์ageออก ตอนนี้คลาสนี้มาถึงคุณแล้ว Catมันตรงตามความต้องการของคุณอย่างเต็มที่ เพราะคุณแค่ต้องการอ็อบเจ็กต์ในโปรแกรม แต่คุณต้องการพวกมันในสนามเดียวกันนั้นage! นี่เป็นปัญหา: เราไม่สามารถเข้าถึงฟิลด์ได้ เนื่องจากมี modifier privateและ getters และ setters ถูกลบออกโดยผู้ที่จะเป็นผู้พัฒนาคลาสนี้ :/ การสะท้อนกลับสามารถช่วยเราในสถานการณ์นี้ได้เช่นกัน! Catเรามีสิทธิ์เข้าถึงรหัสชั้นเรียน : อย่างน้อยเราก็สามารถค้นหาได้ว่ามีฟิลด์ใดบ้างและเรียกว่าอะไร ด้วยข้อมูลนี้ เราจึงสามารถแก้ไขปัญหาของเราได้:
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เข้าถึงฟิลด์ageโดยใช้วิธีgetDeclaredField()การ มันทำให้เราสามารถรับฟิลด์อายุเป็นวัตถุField ageได้ แต่นี่ยังไม่เพียงพอ เนื่องจากprivateไม่สามารถกำหนดค่าฟิลด์ได้ง่ายๆ ในการดำเนินการนี้ คุณต้องทำให้ช่อง "พร้อมใช้งาน" โดยใช้วิธีsetAccessible():
age.setAccessible(true);
ฟิลด์เหล่านั้นที่ทำสิ่งนี้สามารถกำหนดค่าได้:
age.set(cat, 6);
อย่างที่คุณเห็นเรามีตัวตั้งค่าประเภทหนึ่งที่กลับหัว: เรากำหนดField ageค่าให้กับฟิลด์และส่งวัตถุที่ควรกำหนดฟิลด์นี้ให้กับมันด้วย ลองใช้วิธีการของเราmain()แล้วดู:

Cat{name='Barsik', age=6}
เยี่ยมมาก เราทำได้หมด! :) เรามาดูกันว่าเรามีความเป็นไปได้อื่นๆ อีกบ้าง...

วิธีเรียกเมธอดของวัตถุตามชื่อ

ลองเปลี่ยนสถานการณ์จากตัวอย่างก่อนหน้านี้เล็กน้อย สมมติว่าผู้พัฒนาคลาสCatทำผิดพลาดกับฟิลด์ - ทั้งสองฟิลด์พร้อมใช้งาน มี getters และ setters สำหรับฟิลด์เหล่านั้น ทุกอย่างก็โอเค ปัญหาแตกต่างออกไป: เขาสร้างวิธีการส่วนตัวที่เราต้องการอย่างแน่นอน:
private void sayMeow() {

   System.out.println("Meow!");
}
เป็นผลให้เราจะสร้างวัตถุCatในโปรแกรมของเรา แต่จะไม่สามารถเรียกวิธีการของพวกเขา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();
   }
}
ที่นี่เราดำเนินการในลักษณะเดียวกับในสถานการณ์ที่เข้าถึงสนามส่วนตัว ก่อนอื่น เราได้วิธีการที่เราต้องการ ซึ่งถูกห่อหุ้มไว้ใน class object Method:
Method sayMeow = clazz.getDeclaredMethod("sayMeow");
ด้วยความช่วยเหลือgetDeclaredMethod()คุณสามารถ "เข้าถึง" วิธีการส่วนตัวได้ ต่อไปเราจะทำให้วิธีการนั้นสามารถเรียกได้:
sayMeow.setAccessible(true);
และสุดท้าย เราเรียกเมธอดบนวัตถุที่ต้องการ:
sayMeow.invoke(cat);
การเรียกเมธอดก็ดูเหมือน "การเรียกกลับด้าน": เราคุ้นเคยกับการชี้วัตถุไปยังวิธีที่ต้องการโดยใช้จุด ( cat.sayMeow()) และเมื่อทำงานกับการสะท้อนกลับ เราจะส่งต่อไปยังเมธอดของออบเจ็กต์ที่ต้องการเรียก . เรามีอะไรในคอนโซล?

Meow!
ทุกอย่างได้ผล! :) ตอนนี้คุณคงได้เห็นแล้วว่ากลไกการสะท้อนกลับใน Java มอบความเป็นไปได้มากมายเพียงใด ในสถานการณ์ที่ยากลำบากและไม่คาดคิด (เช่นในตัวอย่างที่มีคลาสจากไลบรารีแบบปิด) มันสามารถช่วยเราได้มากจริงๆ อย่างไรก็ตาม เช่นเดียวกับพลังอันยิ่งใหญ่อื่นๆ มันยังแสดงถึงความรับผิดชอบอันยิ่งใหญ่ด้วย ข้อเสียของการไตร่ตรองได้เขียนไว้ในส่วนพิเศษบนเว็บไซต์ของ Oracle มีข้อเสียเปรียบหลักสามประการ:
  1. ผลผลิตลดลง วิธีการที่เรียกว่าใช้การสะท้อนกลับมีประสิทธิภาพต่ำกว่าวิธีที่เรียกว่าปกติ

  2. มีข้อจำกัดด้านความปลอดภัย กลไกการสะท้อนช่วยให้คุณเปลี่ยนพฤติกรรมของโปรแกรมระหว่างรันไทม์ได้ แต่ในสภาพแวดล้อมการทำงานของคุณในโครงการจริงอาจมีข้อ จำกัด ที่ไม่อนุญาตให้คุณทำเช่นนี้

  3. ความเสี่ยงจากการเปิดเผยข้อมูลภายใน สิ่งสำคัญคือต้องเข้าใจว่าการใช้การสะท้อนกลับเป็นการละเมิดหลักการของการห่อหุ้มโดยตรง: ทำให้เราสามารถเข้าถึงฟิลด์ส่วนตัว วิธีการ ฯลฯ ฉันคิดว่าไม่จำเป็นต้องอธิบายว่าควรใช้การละเมิดหลักการ OOP โดยตรงและร้ายแรงเฉพาะในกรณีที่ร้ายแรงที่สุดเท่านั้น เมื่อไม่มีวิธีอื่นในการแก้ปัญหาด้วยเหตุผลที่อยู่นอกเหนือการควบคุมของคุณ

ใช้กลไกการสะท้อนอย่างชาญฉลาดและในสถานการณ์ที่ไม่สามารถหลีกเลี่ยงได้เท่านั้น และอย่าลืมข้อบกพร่องของมันด้วย นี่เป็นการสรุปการบรรยายของเรา! มันค่อนข้างใหญ่ แต่วันนี้คุณได้เรียนรู้สิ่งใหม่มากมาย :)
ความคิดเห็น
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION