Reflection API มีไว้เพื่ออะไร?
กลไกการสะท้อนกลับของ Java ช่วยให้นักพัฒนาทำการเปลี่ยนแปลงและรับข้อมูลเกี่ยวกับคลาส อินเทอร์เฟซ ฟิลด์ และเมธอดขณะรันไทม์โดยไม่ทราบชื่อ
Reflection API ยังให้คุณสร้างอ็อบเจกต์ใหม่ วิธีการเรียก และรับหรือตั้งค่าฟิลด์
มาทำรายการทุกสิ่งที่คุณสามารถทำได้โดยใช้การไตร่ตรอง:
- ระบุ/กำหนดคลาสของวัตถุ
- รับข้อมูลเกี่ยวกับตัวดัดแปลงคลาส ฟิลด์ เมธอด ค่าคงที่ ตัวสร้าง และคลาสระดับสูง
- ค้นหาวิธีการที่เป็นของอินเทอร์เฟซที่ใช้งาน
- สร้างอินสแตนซ์ของคลาสที่ไม่รู้จักชื่อคลาสจนกว่าโปรแกรมจะถูกเรียกใช้งาน
- รับและตั้งค่าของฟิลด์อินสแตนซ์ตามชื่อ
- เรียกวิธีการอินสแตนซ์ด้วยชื่อ
เทคโนโลยี Java สมัยใหม่เกือบทั้งหมดใช้การสะท้อนกลับ มันรองรับเฟรมเวิร์กและไลบรารี Java / Java EE ส่วนใหญ่ในปัจจุบัน เช่น:
- Spring frameworks สำหรับสร้างเว็บแอพพลิเคชั่น
- กรอบการทดสอบJUnit
หากคุณไม่เคยพบกลไกเหล่านี้มาก่อน คุณอาจสงสัยว่าเหตุใดจึงจำเป็นทั้งหมดนี้ คำตอบค่อนข้างง่าย แต่ก็คลุมเครือมาก การสะท้อนจะเพิ่มความยืดหยุ่นและความสามารถในการปรับแต่งแอปพลิเคชันและโค้ดของเราอย่างมาก
แต่มีข้อดีและข้อเสียเสมอ ลองพูดถึงข้อเสียบางประการ:
- การละเมิดความปลอดภัยของแอปพลิเคชัน การสะท้อนกลับช่วยให้เราเข้าถึงรหัสที่เราไม่ควร (การละเมิดการห่อหุ้ม)
- ข้อจำกัดด้านความปลอดภัย การสะท้อนต้องการสิทธิ์รันไทม์ที่ไม่มีให้สำหรับระบบที่เรียกใช้ตัวจัดการความปลอดภัย
- ประสิทธิภาพต่ำ การสะท้อนกลับใน Java กำหนดประเภทแบบไดนามิกโดยการสแกนclasspathเพื่อค้นหาคลาสที่จะโหลด สิ่งนี้จะลดประสิทธิภาพของโปรแกรม
- ดูแลรักษายาก รหัสที่ใช้การสะท้อนนั้นอ่านและดีบักได้ยาก มีความยืดหยุ่นน้อยกว่าและดูแลรักษายากกว่า
การทำงานกับคลาสโดยใช้ Reflection API
การดำเนินการสะท้อนทั้งหมดเริ่มต้นด้วยวัตถุjava.lang.Class สำหรับออบเจกต์แต่ละประเภทจะมีการสร้าง อินสแตนซ์ที่ไม่เปลี่ยนรูปของ java.lang.Class มีเมธอดสำหรับการรับคุณสมบัติของอ็อบเจกต์ การสร้างอ็อบเจกต์ใหม่ และวิธีการเรียก
มาดูรายการเมธอดพื้นฐานสำหรับการทำงานกับjava.lang.Class :
วิธี | การกระทำ |
---|---|
สตริง getName(); | ส่งกลับชื่อของชั้นเรียน |
int getModifiers (); | ส่งคืนตัวแก้ไขการเข้าถึง |
แพ็คเกจ getPackage (); | ส่งคืนข้อมูลเกี่ยวกับแพ็คเกจ |
คลาส getSuperclass(); | ส่งคืนข้อมูลเกี่ยวกับคลาสพาเรนต์ |
คลาส [] getInterfaces (); | ส่งกลับอาร์เรย์ของอินเทอร์เฟซ |
ตัวสร้าง [] getConstructors (); | ส่งกลับข้อมูลเกี่ยวกับตัวสร้างคลาส |
ฟิลด์[] getFields(); | ส่งกลับเขตข้อมูลของชั้นเรียน |
ฟิลด์ getField (ชื่อฟิลด์สตริง); | ส่งกลับเขตข้อมูลเฉพาะของชั้นเรียนตามชื่อ |
เมธอด[] getMethods(); | ส่งคืนอาร์เรย์ของเมธอด |
วิธีการเหล่านี้เป็นวิธีการที่สำคัญที่สุดในการรับข้อมูลเกี่ยวกับคลาส ส่วนต่อประสาน ฟิลด์ และเมธอด นอกจากนี้ยังมีวิธีการที่ให้คุณรับหรือตั้งค่าฟิลด์ และเข้าถึงฟิลด์ส่วนตัว เราจะดูพวกเขาในภายหลัง
ตอนนี้เราจะพูดถึงการสร้างjava.lang.Classเอง เรามีสามวิธีในการทำเช่นนี้
1. การใช้ Class.forName
ในแอปพลิเคชันที่กำลังทำงานอยู่ คุณต้องใช้ เมธอด forName(String className)เพื่อรับคลาส
รหัสนี้แสดงให้เห็นว่าเราสามารถสร้างชั้นเรียนโดยใช้การสะท้อนกลับได้อย่างไร มาสร้าง คลาส บุคคลที่เราสามารถทำงานด้วย:
package com.company;
public class Person {
private int age;
private String name;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
และส่วนที่สองของตัวอย่างของเราคือโค้ดที่ใช้การสะท้อน:
public class TestReflection {
public static void main(String[] args) {
try {
Class<?> aClass = Class.forName("com.company.Person");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
วิธีการนี้เป็นไปได้หากทราบชื่อเต็มของคลาส จากนั้นคุณจะได้รับคลาสที่เกี่ยวข้องโดยใช้เมธอดClass.forName()แบบ คงที่ วิธีนี้ไม่สามารถใช้กับประเภทดั้งเดิมได้
2. การใช้ .class
หากมีประเภทอยู่ แต่ไม่มีอินสแตนซ์ของประเภทนั้น คุณสามารถรับคลาสได้โดยเพิ่ม.classให้กับชื่อประเภท นี่เป็นวิธีที่ง่ายที่สุดในการรับคลาสประเภทดั้งเดิม
Class aClass = Person.class;
3. การใช้ .getClass()
หากมีออบเจกต์ วิธีที่ง่ายที่สุดในการรับคลาสคือการเรียกobject.getClass( )
Person person = new Person();
Class aClass = person.getClass();
อะไรคือความแตกต่างระหว่างสองแนวทางสุดท้าย?
ใช้A.classถ้าคุณรู้ว่าคุณสนใจวัตถุคลาสใดในขณะเขียนโค้ด หากไม่มีอินสแตนซ์ คุณควรใช้. class
รับวิธีการของชั้นเรียน
มาดูเมธอดที่ส่งคืนเมธอดของคลาสของเรา: getDeclaredMethods ()และgetMethods()
getDeclaredMethods()ส่งคืนอาร์เรย์ที่มี วัตถุ เมธอดสำหรับเมธอดที่ประกาศทั้งหมดของคลาสหรืออินเทอร์เฟซที่แสดงโดยออบเจ็กต์คลาส รวมถึงเมธอดสาธารณะ ส่วนตัว ดีฟอลต์ และป้องกัน แต่ไม่ใช่เมธอดที่สืบทอดมา
getMethods()ส่งคืนอาร์เรย์ที่มี วัตถุ เมธอดสำหรับเมธอดสาธารณะทั้งหมดของคลาสหรืออินเตอร์เฟสที่แสดงโดยคลาสอ็อบเจกต์ — คลาสหรืออินเตอร์เฟสที่ประกาศโดยคลาสหรืออินเตอร์เฟส รวมถึงที่สืบทอดมาจากซูเปอร์คลาสและซูเปอร์อินเตอร์เฟส
ลองมาดูกันว่าแต่ละวิธีทำงานอย่างไร
เริ่มจากgetDeclaredMethods()กัน ก่อน เพื่อช่วยให้เราเข้าใจความแตกต่างระหว่างสองวิธีอีกครั้ง ด้านล่างเราจะทำงานกับคลาสตัวเลข ที่เป็นนามธรรม ลองเขียนเมธอดคงที่ที่จะแปลง อาร์เรย์ เมธอด ของเรา เป็นList<String> :
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class TestReflection {
public static void main(String[] args) {
final Method[] declaredMethods = Number.class.getDeclaredMethods();
List<String> actualMethodNames = getMethodNames(declaredMethods);
actualMethodNames.forEach(System.out::println);
}
private static List<String> getMethodNames(Method[] methods) {
return Arrays.stream(methods)
.map(Method::getName)
.collect(Collectors.toList());
}
}
นี่คือผลลัพธ์ของการเรียกใช้รหัสนี้:
shortValue
intValue
longValue
float floatValue;
ค่าสองเท่า
นี่คือวิธีการที่ประกาศในคลาสNumber getMethods()ส่งคืนอะไร ลองเปลี่ยนสองบรรทัดในตัวอย่าง:
final Method[] methods = Number.class.getMethods();
List<String> actualMethodNames = getMethodNames(methods);
การทำเช่นนี้เราจะเห็นชุดของวิธีการต่อไปนี้:
shortValue
intValue
longValue
float floatValue;
doubleValue
wait
wait
wait
เท่ากับ
String
hashCode
getClass
แจ้งเตือน แจ้ง
เตือนทั้งหมด
เนื่องจากทุกคลาสสืบทอดObjectเมธอดของเราจึงส่งคืนเมธอดสาธารณะของคลาสอ็อบเจกต์
รับเขตข้อมูลของชั้นเรียน
เมธอดgetFieldsและgetDeclaredFieldsใช้เพื่อรับฟิลด์ของคลาส ตัวอย่างเช่น ลองดูที่คลาสLocalDateTime เราจะเขียนรหัสของเราใหม่:
import java.lang.reflect.Field;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class TestReflection {
public static void main(String[] args) {
final Field[] declaredFields = LocalDateTime.class.getDeclaredFields();
List<String> actualFieldNames = getFieldNames(declaredFields);
actualFieldNames.forEach(System.out::println);
}
private static List<String> getFieldNames(Field[] fields) {
return Arrays.stream(fields)
.map(Field::getName)
.collect(Collectors.toList());
}
}
จากการดำเนินการโค้ดนี้ เราได้รับชุดของฟิลด์ที่อยู่ในคลาส LocalDateTime
MAX
serialVersionUID
โดยเปรียบเทียบกับวิธีการตรวจสอบก่อนหน้านี้ มาดูกันว่าจะเกิดอะไรขึ้นหากเราเปลี่ยนรหัสเล็กน้อย:
final Field[] fields = LocalDateTime.class.getFields();
List<String> actualFieldNames = getFieldNames(fields);
เอาท์พุต:
สุด
ตอนนี้เรามาหาความแตกต่างระหว่างวิธีการเหล่านี้
เมธอดgetDeclaredFieldsส่งคืนอาร์เรย์ของ วัตถุ ฟิลด์สำหรับฟิลด์ทั้งหมดที่ประกาศโดยคลาสหรืออินเทอร์เฟซที่แสดงโดยสิ่งนี้ระดับวัตถุ.
เมธอดgetFieldsส่งคืนอาร์เรย์ของ วัตถุ ฟิลด์สำหรับ ฟิลด์ สาธารณะ ทั้งหมด ของคลาสหรืออินเทอร์เฟซที่แสดงโดยระดับวัตถุ.
ทีนี้มาดูภายในLocalDateTime

ชั้นเรียนนาทีและสูงสุดฟิลด์เป็นแบบสาธารณะ ซึ่งหมายความว่าจะมองเห็นได้ผ่านเมธอดgetFields ในทางตรงกันข้ามวันที่,เวลา,serialVersionUIDเมธอดมี ตัวแก้ไข ส่วนตัวซึ่งหมายความว่าจะไม่สามารถมองเห็นได้ผ่านเมธอดgetFieldsแต่เราสามารถรับได้โดยใช้getDeclaredFields นี่คือวิธีที่เราสามารถเข้าถึงวัตถุฟิลด์สำหรับฟิลด์ส่วนตัว
คำอธิบายของวิธีการอื่นๆ
ตอนนี้ถึงเวลาพูดคุยเกี่ยวกับวิธีการบางอย่างของ คลาส คลาสได้แก่ :
วิธี | การกระทำ |
---|---|
รับการปรับเปลี่ยน | รับการปรับเปลี่ยนสำหรับชั้นเรียนของเรา |
รับแพคเกจ | รับแพ็คเกจที่มีคลาสของเรา |
getSuperclass | รับคลาสผู้ปกครอง |
รับอินเทอร์เฟซ | รับอาร์เรย์ของอินเทอร์เฟซที่นำไปใช้โดยคลาส |
รับชื่อ | รับชื่อชั้นที่มีคุณสมบัติครบถ้วน |
getSimpleName | รับชื่อชั้นเรียน |
getModifiers()
ตัวดัดแปลงสามารถเข้าถึงได้โดยใช้ aระดับวัตถุ.
ตัวดัดแปลงเป็นคำหลักเช่นpublic , static , interfaceและอื่น ๆ เรารับตัวปรับแต่งโดยใช้ เมธอด getModifiers() :
Class<Person> personClass = Person.class;
int classModifiers = personClass.getModifiers();
รหัสนี้ตั้งค่าของนานาชาติตัวแปรที่เป็นฟิลด์บิต ตัวแก้ไขการเข้าถึงแต่ละตัวสามารถเปิดหรือปิดได้โดยการตั้งค่าหรือล้างบิตที่เกี่ยวข้อง เราสามารถตรวจสอบตัวดัดแปลงโดยใช้เมธอดใน คลาส java.lang.reflect.Modifier :
import com.company.Person;
import java.lang.reflect.Modifier;
public class TestReflection {
public static void main(String[] args) {
Class<Person> personClass = Person.class;
int classModifiers = personClass.getModifiers();
boolean isPublic = Modifier.isPublic(classModifiers);
boolean isStatic = Modifier.isStatic(classModifiers);
boolean isFinal = Modifier.isFinal(classModifiers);
boolean isAbstract = Modifier.isAbstract(classModifiers);
boolean isInterface = Modifier.isInterface(classModifiers);
System.out.printf("Class modifiers: %d%n", classModifiers);
System.out.printf("Is public: %b%n", isPublic);
System.out.printf("Is static: %b%n", isStatic);
System.out.printf("Is final: %b%n", isFinal);
System.out.printf("Is abstract: %b%n", isAbstract);
System.out.printf("Is interface: %b%n", isInterface);
}
}
จำได้ว่าคำประกาศของบุคคล ของเรา มีลักษณะอย่างไร:
public class Person {
…
}
เราได้ผลลัพธ์ต่อไปนี้:
เป็นสาธารณะ: จริง
เป็นคงที่: เท็จ
เป็นขั้นสุดท้าย: เท็จ
เป็นนามธรรม: เท็จ
เป็นอินเทอร์เฟซ: เท็จ
ถ้าเราทำให้คลาสของเราเป็นนามธรรม เราก็มี:
public abstract class Person { … }
และผลลัพธ์นี้:
เป็นสาธารณะ: จริง
เป็นคงที่: เท็จ
เป็นขั้นสุดท้าย: เท็จ
เป็นนามธรรม: จริง
เป็นอินเทอร์เฟซ: เท็จ
เราเปลี่ยนตัวแก้ไขการเข้าถึง ซึ่งหมายความว่าเรายังเปลี่ยนข้อมูลที่ส่งคืนผ่านเมธอดแบบสแตติกของคลาสตัวแก้ไข
รับแพ็คเกจ ()
เรารู้เพียงคลาสเดียว เราสามารถรับข้อมูลเกี่ยวกับแพ็คเกจของมันได้:
Class<Person> personClass = Person.class;
final Package aPackage = personClass.getPackage();
System.out.println(aPackage.getName());
getSuperclass()
ถ้าเรามีอ็อบเจกต์คลาส เราสามารถเข้าถึงคลาสพาเรนต์ของมันได้:
public static void main(String[] args) {
Class<Person> personClass = Person.class;
final Class<? super Person> superclass = personClass.getSuperclass();
System.out.println(superclass);
}
เราได้รับคลาส Objectที่รู้จักกันดี:
class java.lang.Object
แต่ถ้าคลาสของเรามีคลาสพาเรนต์อื่น เราจะเห็นคลาสนั้นแทน:
package com.company;
class Human {
// Some info
}
public class Person extends Human {
private int age;
private String name;
// Some info
}
ที่นี่เราได้รับคลาสผู้ปกครองของเรา:
class com.company.Human
getInterfaces()
นี่คือวิธีที่เราสามารถรับรายการอินเทอร์เฟซที่คลาสนำไปใช้ได้:
public static void main(String[] args) {
Class<Person> personClass = Person.class;
final Class<?>[] interfaces = personClass.getInterfaces();
System.out.println(Arrays.toString(interfaces));
}
และอย่าลืมเปลี่ยน คลาส บุคคล ของเรา :
public class Person implements Serializable { … }
เอาท์พุต:
คลาสสามารถใช้หลายอินเตอร์เฟส นั่นเป็นเหตุผลที่เราได้รับอาร์เรย์ของระดับวัตถุ ใน Java Reflection API อินเทอร์เฟซจะแสดงด้วยระดับวัตถุ
โปรดทราบ:เมธอดจะส่งคืนเฉพาะอินเทอร์เฟซที่ใช้โดยคลาสที่ระบุ ไม่ใช่คลาสพาเรนต์ หากต้องการดูรายการอินเทอร์เฟซทั้งหมดที่ใช้โดยคลาส คุณต้องอ้างถึงทั้งคลาสปัจจุบันและบรรพบุรุษทั้งหมดของคลาสในสายการสืบทอด
getName() & getSimpleName() & getCanonicalName()
ลองเขียนตัวอย่างเกี่ยวกับคลาสดั้งเดิม คลาสซ้อน คลาสนิรนาม และ คลาส สตริง :
public class TestReflection {
public static void main(String[] args) {
printNamesForClass(int.class, "int class (primitive)");
printNamesForClass(String.class, "String.class (ordinary class)");
printNamesForClass(java.util.HashMap.SimpleEntry.class,
"java.util.HashMap.SimpleEntry.class (nested class)");
printNamesForClass(new java.io.Serializable() {
}.getClass(),
"new java.io.Serializable(){}.getClass() (anonymous inner class)");
}
private static void printNamesForClass(final Class<?> clazz, final String label) {
System.out.printf("%s:%n", label);
System.out.printf("\tgetName()):\t%s%n", clazz.getName());
System.out.printf("\tgetCanonicalName()):\t%s%n", clazz.getCanonicalName());
System.out.printf("\tgetSimpleName()):\t%s%n", clazz.getSimpleName());
System.out.printf("\tgetTypeName():\t%s%n%n", clazz.getTypeName());
}
}
ผลลัพธ์ของโปรแกรมของเรา:
getName()): int
getCanonicalName()): int
getSimpleName()): int
getTypeName(): int
String.class (คลาสสามัญ):
getName()): java.lang.String
getCanonicalName() ): java.lang.String
getSimpleName()): สตริง
getTypeName(): java.lang.String
java.util.HashMap.SimpleEntry.class (คลาสที่ซ้อนกัน):
getName()): java.util.AbstractMap$SimpleEntry
getCanonicalName( )): java.util.AbstractMap.SimpleEntry
getSimpleName()): SimpleEntry
getTypeName(): java.util.AbstractMap$SimpleEntry
ใหม่ java.io.Serializable(){}.getClass() (คลาสภายในที่ไม่ระบุชื่อ):
getName() ): TestReflection$1
getCanonicalName()): null
getSimpleName()):
getTypeName(): TestReflection$1
ตอนนี้มาวิเคราะห์ผลลัพธ์ของโปรแกรมของเรากัน:
-
getName()ส่งคืนชื่อของเอนทิตี
-
getCanonicalName()ส่งคืนชื่อบัญญัติของคลาสฐานตามที่กำหนดโดยข้อกำหนดภาษาจาวา คืนค่า null หากคลาสฐานไม่มีชื่อบัญญัติ (นั่นคือ หากเป็นคลาสในเครื่องหรือไม่ระบุชื่อ หรืออาร์เรย์ที่มีองค์ประกอบประเภทไม่มีชื่อบัญญัติ)
-
getSimpleName()ส่งคืนชื่ออย่างง่ายของคลาสพื้นฐานตามที่ระบุในซอร์สโค้ด ส่งคืนสตริงว่างหากคลาสพื้นฐานไม่ระบุตัวตน
-
getTypeName()ส่งคืนสตริงข้อมูลสำหรับชื่อประเภทนี้
GO TO FULL VERSION