เหตุใดฉันจึงควรใช้การสะท้อน


29

ฉันใหม่กับ Java; จากการศึกษาของฉันฉันอ่านว่าการสะท้อนนั้นใช้เพื่อเรียกชั้นเรียนและวิธีการต่างๆ

ฉันควรใช้การสะท้อนเมื่อใดและอะไรคือความแตกต่างระหว่างการใช้การสะท้อนกับวัตถุที่สร้างอินสแตนซ์และวิธีการโทรในแบบดั้งเดิม



10
กรุณาทำการวิจัยของคุณก่อนโพสต์ มีเนื้อหามากมายใน StackExchange (ตามที่ @Jalayn จดบันทึกไว้) และเว็บโดยทั่วไปเกี่ยวกับการสะท้อนกลับ ฉันแนะนำให้คุณอ่านเช่นJava Tutorial on Reflectionและกลับมาหลังจากนั้นถ้าคุณมีคำถามที่เป็นรูปธรรมมากขึ้น
PéterTörök

1
ต้องมีการหลอกเป็นล้านครั้ง
DeadMG

3
โปรแกรมเมอร์มืออาชีพมากกว่าสองสามคนจะตอบว่า "บ่อยที่สุดเท่าที่จะเป็นไปได้
Ross Patterson

คำตอบ:


38
  • Reflection นั้นช้ากว่าการเรียกเมธอดด้วยชื่อเพราะต้องตรวจสอบเมทาดาทาในไบต์รหัสแทนที่จะใช้ที่อยู่และค่าคงที่ที่คอมไพล์แล้ว

  • การสะท้อนยังมีประสิทธิภาพยิ่งขึ้น: คุณสามารถเรียกคืนคำจำกัดความของสมาชิกprotectedหรือลบการป้องกันและจัดการมันราวกับว่ามันถูกประกาศว่าไม่แน่นอน! เห็นได้ชัดว่าสิ่งนี้ทำลายการรับประกันจำนวนมากที่ภาษาปกติใช้กับโปรแกรมของคุณและอาจเป็นอันตรายอย่างยิ่งfinal

และสิ่งนี้ค่อนข้างอธิบายได้ว่าควรใช้เมื่อไร ตามปกติแล้วอย่า ถ้าคุณต้องการเรียกวิธีการเพียงแค่เรียกมัน ถ้าคุณต้องการที่จะกลายพันธุ์สมาชิกเพียงแค่ประกาศว่ามันไม่แน่นอนแทนที่จะกลับไปด้านหลังคอมไพล์

การใช้งานไตร่ตรองที่เป็นประโยชน์อย่างหนึ่งในโลกแห่งความเป็นจริงคือเมื่อทำการเขียนเฟรมเวิร์กที่ต้องทำงานร่วมกันกับคลาสที่ผู้ใช้กำหนดซึ่งผู้เขียนเฟรมเวิร์กไม่ทราบว่าสมาชิก (หรือแม้แต่คลาส) จะเป็นอย่างไร การสะท้อนกลับช่วยให้พวกเขาจัดการกับชั้นเรียนใด ๆ โดยไม่ต้องรู้ล่วงหน้า ตัวอย่างเช่นฉันไม่คิดว่าจะเป็นไปได้ที่จะเขียนไลบรารี่ที่มุ่งเน้นด้านที่ซับซ้อนโดยไม่มีการสะท้อนกลับ

เป็นอีกตัวอย่างหนึ่งที่ JUnit ใช้ในการไตร่ตรองเล็กน้อยมันจะแจกแจงวิธีการทั้งหมดในชั้นเรียนของคุณโดยถือว่าทั้งหมดนั้นเรียกว่าtestXXXเป็นวิธีการทดสอบและดำเนินการเฉพาะที่ แต่ตอนนี้สามารถทำได้ดีกว่าด้วยคำอธิบายประกอบแทนและในความเป็นจริง JUnit 4 ได้ย้ายไปที่คำอธิบายประกอบเป็นส่วนใหญ่แทน


6
"มีประสิทธิภาพมากขึ้น" ต้องการการดูแล คุณไม่จำเป็นต้องไตร่ตรองเพื่อให้ได้ทัวริงครบถ้วนดังนั้นจึงไม่มีการคำนวณที่ต้องการการสะท้อนกลับ แน่นอนว่าทัวริงสมบูรณ์ไม่ได้พูดอะไรเลยเกี่ยวกับพลังงานชนิดอื่น ๆ เช่นความสามารถของ I / O และแน่นอนการสะท้อนกลับ
Steve314

1
การสะท้อนกลับไม่จำเป็นว่า "ช้ากว่า" มากนัก คุณสามารถใช้การสะท้อนหนึ่งครั้งเพื่อสร้างรหัสทางเรียกแบบ wrapper
SK-logic

2
คุณจะรู้ได้เมื่อคุณต้องการ ฉันมักจะสงสัยว่าทำไม (นอกเหนือจากรุ่นภาษา) จะต้องมี จากนั้นทันใดนั้นฉันก็ ... ต้องเดินขึ้นและลงเครือข่ายพ่อแม่ / ลูกเพื่อดูข้อมูล / poke เมื่อฉันได้รับพาเนลจาก devs อื่น ๆ เพื่อป๊อปอัพเป็นหนึ่งในระบบที่ฉันดูแล
Brian Knoblauch

@ SK-logic: จริงๆแล้วสำหรับการสร้าง bytecode คุณไม่จำเป็นต้องไตร่ตรองเลย (การสะท้อนกลับไม่มี API สำหรับการจัดการ bytecode เลย!)
โจอาคิมซาวเออร์

1
@JoachimSauer แน่นอน แต่คุณจะต้องมี API การสะท้อนเพื่อโหลด bytecode ที่สร้างขึ้นนี้
SK-logic

15

ฉันเป็นเหมือนคุณอีกครั้งฉันไม่รู้เรื่องการสะท้อนมากนัก - แต่ก็ไม่ได้ - แต่ฉันใช้มันครั้งเดียว

ฉันมีชั้นเรียนสองชั้นในและแต่ละชั้นมีวิธีการมากมาย

ฉันต้องการเรียกใช้วิธีการทั้งหมดในชั้นในและการเรียกใช้ด้วยตนเองจะทำงานได้มากเกินไป

ด้วยการไตร่ตรองฉันสามารถเรียกใช้วิธีการเหล่านี้ทั้งหมดในโค้ดเพียง 2-3 บรรทัดแทนที่จะเป็นจำนวนวิธี


4
ทำไมต้องลงคะแนน
Mahmoud Hossam

1
Upvoting สำหรับคำนำ
Dmitry Minkovsky

1
@MahmoudHossam อาจไม่ใช่วิธีปฏิบัติที่ดีที่สุด แต่คำตอบของคุณแสดงให้เห็นถึงกลยุทธ์สำคัญที่สามารถนำไปใช้ได้
ankush981

13

ฉันจะใช้การไตร่ตรองเป็นสามกลุ่ม:

  1. ยกตัวอย่างชั้นเรียนโดยพลการ ตัวอย่างเช่นในกรอบการฉีดขึ้นต่อกันคุณอาจประกาศว่าอินเตอร์เฟส ThingDoer ถูกใช้งานโดยคลาส NetworkThingDoer กรอบจะค้นหาตัวสร้างของ NetworkThingDoer และสร้างอินสแตนซ์
  2. การจัดรูปแบบอื่น ๆ ตัวอย่างเช่นการแมปวัตถุกับ getters และการตั้งค่าที่เป็นไปตามแบบแผน bean กับ JSON และกลับมาอีกครั้ง รหัสไม่ทราบชื่อของเขตข้อมูลหรือวิธีการจริง ๆ เพียงตรวจสอบชั้นเรียน
  3. การตัดคลาสในเลเยอร์ของการเปลี่ยนเส้นทาง (อาจเป็นว่ารายการนั้นไม่ได้โหลดจริง แต่เป็นเพียงตัวชี้ไปยังสิ่งที่รู้วิธีดึงจากฐานข้อมูล) หรือแกล้งคลาสทั้งหมด (jMock จะสร้างคลาสสังเคราะห์ที่ใช้อินเทอร์เฟซ เพื่อจุดประสงค์ในการทดสอบ)

นี่คือคำอธิบายที่ดีที่สุดของการสะท้อนที่ฉันพบใน StackExchange คำตอบส่วนใหญ่ทำซ้ำสิ่งที่ Java Trail พูด (ซึ่งก็คือ "คุณสามารถเข้าถึงคุณสมบัติเหล่านี้ได้" แต่ไม่ใช่เหตุผลที่คุณต้องการ) ให้ตัวอย่างของการทำสิ่งต่าง ๆ ที่สะท้อนได้ง่ายกว่าโดยไม่ต้องทำหรือให้คำตอบที่คลุมเครือ ใช้มัน คำตอบนี้ให้ตัวอย่างที่ถูกต้องสามตัวอย่างที่ไม่สามารถแก้ไขได้โดย JVM ขอขอบคุณ!
ndm13

3

Reflection อนุญาตให้โปรแกรมทำงานกับโค้ดที่อาจไม่มีอยู่และทำได้ด้วยวิธีที่เชื่อถือได้

"รหัสปกติ" มีตัวอย่างข้อมูล URLConnection c = nullโดยการปรากฏตัวที่แท้จริงทำให้โหลดระดับชั้นโหลด URLConnection เป็นส่วนหนึ่งของการโหลดชั้นนี้โยนข้อยกเว้น ClassNotFound และออกจาก

Reflection อนุญาตให้คุณโหลดคลาสที่ขึ้นอยู่กับชื่อของพวกเขาในรูปแบบสตริงและทดสอบคุณสมบัติต่างๆ (มีประโยชน์สำหรับหลาย ๆ รุ่นนอกการควบคุมของคุณ) ก่อนเปิดคลาสจริงที่ขึ้นอยู่กับพวกเขา ตัวอย่างทั่วไปคือรหัสเฉพาะ OS X ที่ใช้ในการทำให้โปรแกรม Java ดูเนทีฟภายใต้ OS X ซึ่งไม่มีอยู่ในแพลตฟอร์มอื่น


2

พื้นฐานการไตร่ตรองหมายถึงการใช้รหัสของโปรแกรมของคุณเป็นข้อมูล

ดังนั้นการใช้ภาพสะท้อนอาจเป็นความคิดที่ดีเมื่อรหัสโปรแกรมของคุณเป็นแหล่งข้อมูลที่มีประโยชน์ (แต่มีการแลกเปลี่ยนกันดังนั้นจึงอาจไม่ใช่ความคิดที่ดีเสมอไป)

ตัวอย่างเช่นพิจารณาคลาสที่เรียบง่าย:

public class Foo {
  public int value;
  public string anotherValue;
}

และคุณต้องการสร้าง XML จากมัน คุณสามารถเขียนโค้ดเพื่อสร้าง XML:

public XmlNode generateXml(Foo foo) {
  XmlElement root = new XmlElement("Foo");
  XmlElement valueElement = new XmlElement("value");
  valueElement.add(new XmlText(Integer.toString(foo.value)));
  root.add(valueElement);
  XmlElement anotherValueElement = new XmlElement("anotherValue");
  anotherValueElement.add(new XmlText(foo.anotherValue));
  root.add(anotherValueElement);
  return root;
}

แต่นี่เป็นรหัสสำเร็จรูปจำนวนมากและทุกครั้งที่คุณเปลี่ยนคลาสคุณต้องอัปเดตรหัส จริงๆคุณสามารถอธิบายสิ่งที่รหัสนี้เป็น

  • สร้างองค์ประกอบ XML ด้วยชื่อของคลาส
  • สำหรับแต่ละคุณสมบัติของคลาส
    • สร้างองค์ประกอบ XML ด้วยชื่อของคุณสมบัติ
    • ใส่ค่าของคุณสมบัติลงในองค์ประกอบ XML
    • เพิ่มองค์ประกอบ XML ให้กับราก

นี่คืออัลกอริทึมและอินพุตของอัลกอริทึมคือคลาส: เราต้องการชื่อและชื่อประเภทและค่าของคุณสมบัติ นี่คือที่สะท้อนมา: มันช่วยให้คุณเข้าถึงข้อมูลนี้ Java ช่วยให้คุณตรวจสอบประเภทโดยใช้วิธีการของClassชั้นเรียน

บางกรณีใช้งานเพิ่มเติม:

  • กำหนด URL ในเว็บเซิร์ฟเวอร์ตามชื่อเมธอดของคลาสและพารามิเตอร์ URL ตามอาร์กิวเมนต์เมธอด
  • แปลงโครงสร้างของคลาสให้เป็นข้อกำหนดประเภท GraphQL
  • เรียกวิธีการทุกคลาสที่มีชื่อขึ้นต้นด้วย "test" เป็นกรณีทดสอบหน่วย

อย่างไรก็ตามการสะท้อนกลับทั้งหมดหมายถึงไม่เพียง แต่ดูรหัสที่มีอยู่ (ซึ่งโดยตัวมันเองเรียกว่า "วิปัสสนา") แต่ยังแก้ไขหรือสร้างรหัส มีสองกรณีการใช้งานที่โดดเด่นใน Java สำหรับนี้: พร็อกซี่และ mocks

สมมติว่าคุณมีอินเทอร์เฟซ:

public interface Froobnicator {
  void froobnicateFruits(List<Fruit> fruits);
  void froobnicateFuel(Fuel fuel);
  // lots of other things to froobnicate
}

และคุณมีการนำไปใช้งานที่ทำสิ่งที่น่าสนใจ:

public class PowerFroobnicator implements Froobnicator {
  // awesome implementations
}

และในความเป็นจริงคุณมีการใช้งานที่สองเช่นกัน:

public class EnergySaverFroobnicator implements Froobnicator {
  // efficient implementations
}

ตอนนี้คุณยังต้องการบันทึกผลลัพธ์บางส่วน; คุณเพียงต้องการข้อความบันทึกทุกครั้งที่มีการเรียกใช้เมธอด คุณสามารถเพิ่มบันทึกผลลัพธ์ไปยังทุกวิธีอย่างชัดเจน แต่นั่นน่ารำคาญและคุณต้องทำสองครั้ง หนึ่งครั้งต่อการติดตั้งแต่ละครั้ง (ดังนั้นยิ่งมากขึ้นเมื่อคุณเพิ่มการใช้งานเพิ่มเติม)

คุณสามารถเขียนพร็อกซีแทน:

public class LoggingFroobnicator implements Froobnicator {
  private Logger logger;
  private Froobnicator inner;

  // constructor that sets those two

  public void froobnicateFruits(List<Fruit> fruits) {
    logger.logDebug("froobnicateFruits called");
    inner.froobnicateFruits(fruits);
  }

  public void froobnicateFuel(Fuel fuel) {
    logger.logDebug("froobnicateFuel( called");
    inner.froobnicateFuel(fuel);
  }
  // lots of other things to froobnicate
}

แม้ว่าจะมีรูปแบบซ้ำ ๆ ที่สามารถอธิบายได้ด้วยอัลกอริทึม:

  • พร็อกซีของคนตัดไม้เป็นคลาสที่ใช้อินเทอร์เฟซ
  • มันมีนวกรรมิกที่ใช้การดำเนินการอื่นของอินเทอร์เฟซและคนตัดไม้
  • สำหรับทุกวิธีในส่วนต่อประสาน
    • การใช้งานบันทึกข้อความ "$ methodname ชื่อ"
    • จากนั้นเรียกใช้เมธอดเดียวกันบนอินเทอร์เฟซภายในส่งผ่านอาร์กิวเมนต์ทั้งหมด

และอินพุตของอัลกอริทึมนี้คือนิยามอินเตอร์เฟส

Reflection อนุญาตให้คุณกำหนดคลาสใหม่โดยใช้อัลกอริทึมนี้ Java ช่วยให้คุณทำสิ่งนี้ได้โดยใช้วิธีการของjava.lang.reflect.Proxyชั้นเรียนและมีห้องสมุดที่ให้พลังงานมากกว่าเดิม

ดังนั้นข้อเสียของการสะท้อนคืออะไร?

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

1

การสะท้อนกลับสามารถทำให้ส่วนของโปรแกรมของคุณซิงค์โดยอัตโนมัติเมื่อก่อนหน้านี้คุณจะต้องอัปเดตโปรแกรมของคุณด้วยตนเองเพื่อใช้อินเทอร์เฟซใหม่


5
ราคาที่คุณจ่ายในกรณีนี้คือการที่คุณสูญเสียการตรวจสอบประเภทโดยคอมไพเลอร์และความปลอดภัยในการปรับโครงสร้างใน IDE มันเป็นการแลกเปลี่ยนที่ฉันไม่ต้องการทำ
Barend
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.