CodeGym /จาวาบล็อก /สุ่ม /Reflection API: การสะท้อนกลับ ด้านมืดของชวา
John Squirrels
ระดับ
San Francisco

Reflection API: การสะท้อนกลับ ด้านมืดของชวา

เผยแพร่ในกลุ่ม
สวัสดี พาดาวันหนุ่ม ในบทความนี้ ฉันจะบอกคุณเกี่ยวกับ Force ซึ่งเป็นพลังที่โปรแกรมเมอร์ Java ใช้เฉพาะในสถานการณ์ที่ดูเหมือนจะเป็นไปไม่ได้เท่านั้น ด้านมืดของ Java คือ Reflection API ใน Java การสะท้อนถูกนำไปใช้โดยใช้ Java Reflection API

การสะท้อนของ Java คืออะไร?

มีคำจำกัดความที่สั้น ถูกต้อง และเป็นที่นิยมบนอินเทอร์เน็ต การสะท้อน ( จากภาษาละตินตอนปลาย - เพื่อย้อนกลับ ) เป็นกลไกในการสำรวจข้อมูลเกี่ยวกับโปรแกรมในขณะที่กำลังทำงาน การสะท้อนช่วยให้คุณสำรวจข้อมูลเกี่ยวกับฟิลด์ เมธอด และตัวสร้างคลาส การสะท้อนกลับทำให้คุณสามารถทำงานกับประเภทที่ไม่มีอยู่ในเวลาคอมไพล์ แต่จะพร้อมใช้งานในช่วงเวลารันไทม์ การสะท้อนกลับและโมเดลที่สอดคล้องกันในเชิงตรรกะสำหรับการออกข้อมูลข้อผิดพลาดทำให้สามารถสร้างโค้ดไดนามิกที่ถูกต้องได้ กล่าวอีกนัยหนึ่ง การเข้าใจว่าการสะท้อนทำงานอย่างไรใน Java จะเปิดโอกาสที่น่าอัศจรรย์มากมายให้กับคุณ คุณสามารถเล่นปาหี่คลาสและส่วนประกอบได้อย่างแท้จริง ต่อไปนี้เป็นรายการพื้นฐานของสิ่งที่สะท้อนให้เห็น:
  • เรียนรู้/กำหนดคลาสของวัตถุ
  • รับข้อมูลเกี่ยวกับตัวดัดแปลงของคลาส ฟิลด์ เมธอด ค่าคงที่ ตัวสร้าง และซูเปอร์คลาส
  • ค้นหาวิธีการที่เป็นของส่วนต่อประสานที่ใช้งาน
  • สร้างอินสแตนซ์ของคลาสที่ไม่รู้จักชื่อคลาสจนกว่าจะรันไทม์
  • รับและตั้งค่าฟิลด์ของวัตถุตามชื่อ
  • เรียกเมธอดของอ็อบเจกต์ด้วยชื่อ
Reflection ใช้ในเทคโนโลยี Java สมัยใหม่เกือบทั้งหมด เป็นการยากที่จะจินตนาการว่า Java ในฐานะแพลตฟอร์ม จะได้รับการนำไปใช้อย่างแพร่หลายโดยปราศจากการไตร่ตรอง เป็นไปได้มากที่จะไม่มี ตอนนี้คุณคุ้นเคยกับการสะท้อนกลับเป็นแนวคิดทางทฤษฎีแล้ว เราจะไม่เรียนรู้วิธีการทั้งหมดของ Reflection API เพียงวิธีการที่คุณจะพบในทางปฏิบัติ เนื่องจากการสะท้อนเกี่ยวข้องกับการทำงานกับชั้นเรียน เราจะเริ่มด้วยชั้นเรียนง่ายๆ ที่ชื่อว่าMyClass:

public class MyClass {
   private int number;
   private String name = "default";
//    public MyClass(int number, String name) {
//        this.number = number;
//        this.name = name;
//    }
   public int getNumber() {
       return number;
   }
   public void setNumber(int number) {
       this.number = number;
   }
   public void setName(String name) {
       this.name = name;
   }
   private void printData(){
       System.out.println(number + name);
   }
}
อย่างที่คุณเห็น นี่เป็นคลาสพื้นฐานมาก ตัวสร้างที่มีพารามิเตอร์ถูกใส่ความคิดเห็นโดยเจตนา เราจะกลับมาที่ในภายหลัง หากคุณดูเนื้อหาของชั้นเรียนอย่างระมัดระวัง คุณอาจสังเกตเห็นว่าไม่มีตัวรับสำหรับฟิลด์ชื่อ ฟิลด์ชื่อนั้นถูกทำเครื่องหมายด้วย ตัวดัดแปลงการเข้าถึง ส่วนตัว : เราไม่สามารถเข้าถึงได้นอกคลาส ซึ่งหมายความว่าเราไม่สามารถดึงค่าของมันกลับมาได้ " แล้วปัญหาคืออะไร ?" คุณพูด. "เพิ่มgetterหรือเปลี่ยน access modifier". และคุณจะพูดถูก เว้นแต่MyClassอยู่ในไลบรารี AAR ที่คอมไพล์แล้วหรือในโมดูลส่วนตัวอื่นที่ไม่มีความสามารถในการเปลี่ยนแปลง ในทางปฏิบัติ สิ่งนี้เกิดขึ้นตลอดเวลา และโปรแกรมเมอร์ที่ประมาทบางคนก็ลืมที่จะเขียนgetter นี่เป็นเวลาที่ต้องจดจำการไตร่ตรอง! ลองไปที่ฟิลด์ชื่อส่วนตัวของMyClassชั้นเรียน:

public static void main(String[] args) {
   MyClass myClass = new MyClass();
   int number = myClass.getNumber();
   String name = null; // No getter =(
   System.out.println(number + name); // Output: 0null
   try {
       Field field = myClass.getClass().getDeclaredField("name");
       field.setAccessible(true);
       name = (String) field.get(myClass);
   } catch (NoSuchFieldException | IllegalAccessException e) {
       e.printStackTrace();
   }
   System.out.println(number + name); // Output: 0default
}
มาวิเคราะห์กันว่าเกิดอะไรขึ้น ใน Java มีคลาสที่ยอดเยี่ยมที่เรียกว่าClass. มันแสดงถึงคลาสและอินเทอร์เฟซในแอปพลิเคชัน Java ที่เรียกใช้งานได้ เราจะไม่พูดถึงความสัมพันธ์ระหว่างClassและClassLoaderเนื่องจากนั่นไม่ใช่หัวข้อของบทความนี้ ถัดไป ในการดึงฟิลด์ของคลาสนี้ คุณต้องเรียกใช้getFields()เมธอด เมธอดนี้จะส่งคืนฟิลด์ที่สามารถเข้าถึงได้ทั้งหมดของคลาสนี้ สิ่งนี้ไม่ได้ผลสำหรับเรา เนื่องจากฟิลด์ของเราเป็นแบบส่วนตัวดังนั้นเราจึงใช้getDeclaredFields()วิธี นี้ วิธีนี้ยังส่งคืนอาร์เรย์ของฟิลด์คลาส แต่ตอนนี้รวมถึงฟิลด์ส่วนตัวและฟิลด์ที่มีการป้องกัน ในกรณีนี้ เราทราบชื่อฟิลด์ที่เราสนใจ เราจึงสามารถใช้เมธอดได้getDeclaredField(String)โดยที่Stringคือชื่อฟิลด์ที่ต้องการ บันทึก: getFields()และgetDeclaredFields()อย่าส่งคืนฟิลด์ของคลาสพาเรนต์! ยอดเยี่ยม. เรามีFieldวัตถุที่อ้างอิงถึงชื่อ ของ เรา เนื่องจากฟิลด์นี้ไม่ได้เปิดเผยต่อสาธารณะเราจึงต้องให้สิทธิ์การเข้าถึงเพื่อทำงานกับฟิลด์นั้น วิธีsetAccessible(true)การช่วยให้เราดำเนินการต่อไป ตอนนี้ ฟิลด์ ชื่ออยู่ภายใต้การควบคุมของเราอย่างสมบูรณ์! คุณสามารถเรียกค่าของมันได้โดยการเรียก เมธอด Fieldของอ็อบเจก ต์ โดยที่ อินสแตนซ์ของคลาส ของเราอยู่ get(Object)ที่ไหน เราแปลงประเภทเป็นและกำหนดค่าให้กับตัวแปรชื่อ ของเรา หากเราไม่พบตัวตั้งค่าสำหรับตั้งค่าใหม่ให้กับฟิลด์ชื่อ คุณสามารถใช้ วิธี การตั้งค่า : ObjectMyClassString

field.set(myClass, (String) "new value");
ยินดีด้วย! คุณเพิ่งเข้าใจพื้นฐานของการไตร่ตรองและเข้าถึง ฟิลด์ ส่วนตัว ! ให้ความสนใจกับtry/catchบล็อกและประเภทของข้อยกเว้นที่ได้รับการจัดการ IDE จะบอกคุณเองว่าต้องมีการแสดงตนของพวกเขา แต่คุณสามารถบอกได้อย่างชัดเจนจากชื่อของพวกเขาว่าทำไมพวกเขาถึงมาที่นี่ กำลังเดินทางไป! ดังที่คุณอาจสังเกตเห็นว่าMyClassคลาสของเรามีวิธีการแสดงข้อมูลเกี่ยวกับข้อมูลคลาสอยู่แล้ว:

private void printData(){
       System.out.println(number + name);
   }
แต่โปรแกรมเมอร์คนนี้ทิ้งรอยนิ้วมือไว้ที่นี่ด้วย เมธอดนี้มี ตัวปรับแต่งการเข้าถึง ส่วนตัวและเราต้องเขียนโค้ดเพื่อแสดงข้อมูลเองทุกครั้ง ช่างเป็นอะไรที่ยุ่งเหยิง ภาพสะท้อนของเราหายไปไหน? เขียนฟังก์ชันต่อไปนี้:

public static void printData(Object myClass){
   try {
       Method method = myClass.getClass().getDeclaredMethod("printData");
       method.setAccessible(true);
       method.invoke(myClass);
   } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
       e.printStackTrace();
   }
}
ขั้นตอนนี้เหมือนกับขั้นตอนที่ใช้สำหรับการดึงข้อมูลฟิลด์ เราเข้าถึงวิธีการที่ต้องการด้วยชื่อและให้สิทธิ์การเข้าถึง และบนMethodวัตถุเราเรียกinvoke(Object, Args)เมธอดซึ่งObjectเป็นอินสแตนซ์ของMyClassคลาส ด้วย Argsเป็นอาร์กิวเมนต์ของเมธอด แม้ว่าของเราไม่มีก็ตาม ตอนนี้เราใช้printDataฟังก์ชั่นเพื่อแสดงข้อมูล:

public static void main(String[] args) {
   MyClass myClass = new MyClass();
   int number = myClass.getNumber();
   String name = null; //?
   printData(myClass); // Output: 0default
   try {
       Field field = myClass.getClass().getDeclaredField("name");
       field.setAccessible(true);
       field.set(myClass, (String) "new value");
       name = (String) field.get(myClass);
   } catch (NoSuchFieldException | IllegalAccessException e) {
       e.printStackTrace();
   }
   printData(myClass);// Output: 0new value
}
เย่! ตอนนี้เราสามารถเข้าถึงวิธีการส่วนตัวของชั้นเรียน แต่จะเกิดอะไรขึ้นถ้าเมธอดมีข้อโต้แย้ง และเหตุใดตัวสร้างจึงแสดงความคิดเห็นออกมา ทุกอย่างเป็นไปตามกำหนดเวลาของมันเอง เป็นที่ชัดเจนจากคำจำกัดความในตอนต้นว่าการสะท้อนช่วยให้คุณสร้างอินสแตนซ์ของคลาสในเวลาทำงาน (ในขณะที่โปรแกรมกำลังทำงาน)! เราสามารถสร้างวัตถุโดยใช้ชื่อเต็มของคลาส ชื่อเต็มของคลาสคือชื่อคลาส รวมถึงพาธของแพ็กเก
Reflection API: การสะท้อนกลับ  ด้านมืดของ Java - 2
ในลำดับชั้นของแพ็คเกจชื่อเต็มของMyClassจะเป็น "reflection.MyClass" นอกจากนี้ยังมีวิธีง่ายๆ ในการเรียนรู้ชื่อชั้นเรียน (ส่งคืนชื่อชั้นเรียนเป็นสตริง):

MyClass.class.getName()
ลองใช้การสะท้อนของ Java เพื่อสร้างอินสแตนซ์ของคลาส:

public static void main(String[] args) {
   MyClass myClass = null;
   try {
       Class clazz = Class.forName(MyClass.class.getName());
       myClass = (MyClass) clazz.newInstance();
   } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
       e.printStackTrace();
   }
   System.out.println(myClass); // Output: created object reflection.MyClass@60e53b93
}
เมื่อแอปพลิเคชัน Java เริ่มทำงาน ไม่ใช่ทุกคลาสที่โหลดลงใน JVM หากโค้ดของคุณไม่ได้อ้างอิงถึงMyClassคลาสClassLoaderซึ่งมีหน้าที่โหลดคลาสลงใน JVM จะไม่โหลดคลาสนั้น นั่นหมายความว่าคุณต้องบังคับClassLoaderให้โหลดและรับคำอธิบายคลาสในรูปแบบของClassตัวแปร นี่คือเหตุผลที่เรามี วิธีการ ชื่อของคลาสที่เราต้องการคำอธิบายอยู่forName(String)ที่ไหน Stringหลังจากได้รับСlassวัตถุแล้ว การเรียกใช้เมธอดnewInstance()จะส่งคืนObjectวัตถุที่สร้างขึ้นโดยใช้คำอธิบายนั้น สิ่งที่เหลืออยู่คือการจัดหาวัตถุนี้ให้กับเราMyClassระดับ. เย็น! นั่นเป็นเรื่องยาก แต่ก็เข้าใจได้ ฉันหวังว่า ตอนนี้เราสามารถสร้างอินสแตนซ์ของคลาสในบรรทัดเดียวได้แล้ว! น่าเสียดายที่วิธีการที่อธิบายไว้จะใช้ได้กับตัวสร้างเริ่มต้นเท่านั้น (ไม่มีพารามิเตอร์) คุณเรียกเมธอดและตัวสร้างด้วยพารามิเตอร์ได้อย่างไร ได้เวลายกเลิกการแสดงความคิดเห็นตัวสร้างของเราแล้ว ตามที่คาดไว้newInstance()ไม่พบตัวสร้างเริ่มต้นและใช้งานไม่ได้อีกต่อไป มาเขียนการสร้างอินสแตนซ์ของคลาสใหม่:

public static void main(String[] args) {
   MyClass myClass = null;
   try {
       Class clazz = Class.forName(MyClass.class.getName());
       Class[] params = {int.class, String.class};
       myClass = (MyClass) clazz.getConstructor(params).newInstance(1, "default2");
   } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {
       e.printStackTrace();
   }
   System.out.println(myClass);// Output: created object reflection.MyClass@60e53b93
}
ควรเรียกใช้ เมธอดgetConstructors()ในนิยามคลาสเพื่อรับตัวสร้างคลาส จากนั้นgetParameterTypes()ควรเรียกใช้เมธอดเพื่อรับพารามิเตอร์ของตัวสร้าง:

Constructor[] constructors = clazz.getConstructors();
for (Constructor constructor : constructors) {
   Class[] paramTypes = constructor.getParameterTypes();
   for (Class paramType : paramTypes) {
       System.out.print(paramType.getName() + " ");
   }
   System.out.println();
}
นั่นทำให้เราได้รับตัวสร้างและพารามิเตอร์ทั้งหมด ในตัวอย่างของฉัน ฉันอ้างถึงตัวสร้างเฉพาะที่มีพารามิเตอร์เฉพาะที่รู้จักก่อนหน้านี้ และในการเรียกคอนสตรัคเตอร์นี้ เราใช้newInstanceวิธีการที่เราส่งผ่านค่าของพารามิเตอร์เหล่านี้ มันจะเหมือนกันเมื่อใช้invokeกับวิธีการโทร สิ่งนี้ทำให้เกิดคำถาม: การเรียกตัวสร้างผ่านการสะท้อนจะมีประโยชน์เมื่อใด ดังที่ได้กล่าวไปแล้วในตอนต้น เทคโนโลยี Java สมัยใหม่ไม่สามารถทำได้หากไม่มี Java Reflection API ตัวอย่างเช่น Dependency Injection (DI) ซึ่งรวมคำอธิบายประกอบเข้ากับการสะท้อนของวิธีการและตัวสร้างเพื่อสร้างDarer ที่เป็นที่นิยมห้องสมุดสำหรับการพัฒนา Android หลังจากอ่านบทความนี้แล้ว คุณสามารถพิจารณาตนเองได้อย่างมั่นใจเกี่ยวกับแนวทางของ Java Reflection API พวกเขาไม่เรียกว่าการสะท้อนด้านมืดของ Java เพื่ออะไร มันทำลายกระบวนทัศน์ OOP อย่างสิ้นเชิง ใน Java การห่อหุ้มจะซ่อนและจำกัดการเข้าถึงส่วนประกอบของโปรแกรมบางอย่างของผู้อื่น เมื่อเราใช้ตัวดัดแปลงส่วนตัว เราตั้งใจให้ฟิลด์นั้นสามารถเข้าถึงได้จากภายในคลาสที่มีอยู่เท่านั้น และเราสร้างสถาปัตยกรรมที่ตามมาของโปรแกรมตามหลักการนี้ ในบทความนี้ เราได้เห็นวิธีที่คุณสามารถใช้การสะท้อนเพื่อบังคับทิศทางของคุณได้ทุกที่ รูปแบบการออกแบบที่สร้างสรรค์Singletonเป็นตัวอย่างที่ดีของสิ่งนี้ในฐานะโซลูชันทางสถาปัตยกรรม แนวคิดพื้นฐานคือคลาสที่ใช้รูปแบบนี้จะมีเพียงหนึ่งอินสแตนซ์ระหว่างการดำเนินการของโปรแกรมทั้งหมด สิ่งนี้ทำได้โดยการเพิ่มตัวดัดแปลงการเข้าถึงส่วนตัวให้กับตัวสร้างเริ่มต้น และคงจะแย่มากถ้าโปรแกรมเมอร์ใช้การสะท้อนกลับและสร้างอินสแตนซ์ของคลาสดังกล่าวให้มากขึ้น อย่างไรก็ตาม เมื่อเร็ว ๆ นี้ฉันได้ยินเพื่อนร่วมงานคนหนึ่งถามคำถามที่น่าสนใจมาก: คลาสที่ใช้รูปแบบ Singleton สามารถสืบทอดได้หรือไม่ เป็นไปได้ไหมว่า ในกรณีนี้ แม้แต่เงาสะท้อนก็ไร้พลัง? แสดงความคิดเห็นของคุณเกี่ยวกับบทความและคำตอบของคุณในความคิดเห็นด้านล่าง และถามคำถามของคุณเองที่นั่น!

อ่านเพิ่มเติม:

ความคิดเห็น
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION