![translation](https://cdn.durumis.com/common/trans.png)
Đây là bài viết được dịch bởi AI.
Chọn ngôn ngữ
Văn bản được tóm tắt bởi AI durumis
- Reflection là một API cho phép truy cập thông tin lớp và thao tác lớp theo ý muốn tại thời điểm chạy.
- Reflection cho phép tạo thể hiện của lớp, truy cập trường và phương thức bất kể bộ điều khiển truy cập, đặc biệt hữu ích trong việc quản lý các mối quan hệ động trong các giai đoạn phát triển quy mô lớn như khung.
- Tuy nhiên, nó có thể vi phạm đóng gói và gây ra hiệu suất thấp, vì vậy tốt nhất là chỉ sử dụng nó khi cần thiết.
Phản chiếu là gì?
Phản chiếu là một API cho phép bạn tạo các thể hiện của lớp mong muốn thông qua các đối tượng loại Class được tải trong vùng heap và cho phép bạn truy cập các trường và phương thức của thể hiện, bất kể trình điều khiển truy cập.
Ở đây, các lớp được tải đề cập đến việc trình tải lớp JVM hoàn thành việc tải tệp lớp và sau đó tạo mộtđối tượng loại Classchứa thông tin về lớp đó và lưu trữ nó trong vùng heap của bộ nhớ. Hãy lưu ý rằng đây khác với các đối tượng được tạo bằng từ khóa new. Nếu bạn không hiểu rõ về đối tượng loại Class này, bạn có thể tham khảo tài liệu JDK cho java.lang.class.
Cách sử dụng
Trước khi sử dụng phản chiếu, bạn cần lấy đối tượng loại Class được tải trong vùng heap. Có tổng cộng 3 cách.
- Lấy bằng cách sử dụng lớp.class
- Lấy bằng cách sử dụng thể hiện.getClass()
- Lấy bằng cách sử dụng Class.forName("tên lớp")
public class Member {
private String name;
protected int age;
public String hobby;
public Member() {
}
public Member(String name, int age, String hobby) {
this.name = name;
this.age = age;
this.hobby = hobby;
}
public void speak(String message) {
System.out.println(message);
}
private void secret() {
System.out.println("Mật khẩu là 1234.");
}
@Override
public String toString() {
return "Member{" +
"name='" + name + '\'' +
", age=" + age +
", hobby='" + hobby + '\'' +
'}';
}
}
public class Main {
public static void main(String[] args) throws ClassNotFoundException {
Class memberClass = Member.class;
System.out.println(System.identityHashCode(memberClass));
Member member = new Member("Jayon", 23, "Phát triển Dara");
Class extends Member> memberClass2 = member.getClass();
System.out.println(System.identityHashCode(memberClass2));
Class> memberClass3 = Class.forName("{tên gói}.Member");
System.out.println(System.identityHashCode(memberClass3));
}
}
// Kết quả chạy
1740000325
1740000325
Bạn có thể thấy rằng các thể hiện loại Class thu được bằng 3 cách đều giống nhau. Bất kể bạn sử dụng phương pháp nào, giá trị băm đều giống nhau, vì vậy bạn có thể sử dụng chúng một cách linh hoạt tùy theo hoàn cảnh.
Bây giờ, thông qua loại Class thu được, bạn có thể tạo thể hiện của lớp đó và truy cập các trường và phương thức của thể hiện, bất kể trình điều khiển truy cập. Đầu tiên, hãy thử tạo thể hiện của lớp đó.
public class Main {
public static void main(String[] args) throws Exception {
// In tất cả các hàm tạo của Member
Member member = new Member();
Class extends Member> memberClass = member.getClass();
Arrays.stream(memberClass.getConstructors()).forEach(System.out::println);
// Tạo thể hiện thông qua hàm tạo mặc định của Member
Constructor extends Member> constructor = memberClass.getConstructor();
Member member2 = constructor.newInstance();
System.out.println("member2 = " + member2);
// Tạo thể hiện thông qua hàm tạo khác của Member
Constructor extends Member> fullConstructor =
memberClass.getConstructor(String.class, int.class, String.class);
Member member3 = fullConstructor.newInstance("Jayon", 23, "Phát triển Dara");
System.out.println("member3 = " + member3);
}
}
// Kết quả chạy
public Member()
public Member(java.lang.String,int,java.lang.String)
member2 = Member{name='null', age=0, hobby='null'}
Bạn có thể sử dụng getConstructor() để lấy hàm tạo và sử dụng newInstance() để động tạo thể hiện Member.
Cuối cùng, hãy thử truy cập các trường và phương thức của thể hiện, bất kể trình điều khiển truy cập.
public class Main {
public static void main(String[] args) throws Exception {
Member member = new Member("Jayon", 23, "Phát triển Dara");
Class extends Member> memberClass = member.getClass();
// Truy cập trường
Field[] fields = memberClass.getDeclaredFields();
for (Field field : fields) {
field.setAccessible(true);
System.out.println(field.get(member));
}
fields[0].set(member, "Jayon2");
System.out.println(member);
// Truy cập phương thức
Method speakMethod = memberClass.getDeclaredMethod("speak", String.class);
speakMethod.invoke(member, "Kiểm tra phản chiếu");
Method secretMethod = memberClass.getDeclaredMethod("secret");
secretMethod.setAccessible(true);
secretMethod.invoke(member);
}
}
// Kết quả chạy
Jayon
23
Phát triển Dara
Member{name='Jayon2', age=23, hobby='Phát triển Dara'}
Kiểm tra phản chiếu
Bạn có thể sử dụng getDeclaredFileds() để lấy tất cả các biến thể hiện của lớp và sử dụng get() để nhận giá trị trường và sử dụng set() để sửa đổi giá trị trường. Lưu ý rằng khi truy cập các trường có trình điều khiển truy cập private, bạn cần truyền true làm đối số cho setAccessible().
Phương thức cũng có thể được lấy bằng getDeclaredMethod(). Khi đó, bạn cần truyền tên phương thức và kiểu dữ liệu của tham số làm đối số. Tương tự, khi truy cập các phương thức có trình điều khiển truy cập private, bạn cần đặt true làm đối số cho setAccessible(). Cuối cùng, bạn có thể sử dụng phương thức invoke() để gọi phương thức thu được bằng API phản chiếu.
Ưu điểm và nhược điểm
- Ưu điểm
- Nó có tính linh hoạt, cho phép bạn tạo thể hiện của lớp tại thời điểm chạy và truy cập các trường và phương thức, bất kể trình điều khiển truy cập, để thực hiện các tác vụ cần thiết.
- Nhược điểm
- Vi phạm đóng gói.
- Không thể kiểm tra kiểu tương ứng tại thời điểm biên dịch vì thể hiện được tạo tại thời điểm chạy.
- Khó xác định luồng hoạt động cụ thể vì thể hiện được tạo tại thời điểm chạy.
- Hiệu suất chậm hơn so với việc sử dụng phản chiếu để truy cập trường và phương thức. (Không chậm trong mọi trường hợp.)
Lý do sử dụng
API phản chiếu cho phép bạn truy cập thông tin lớp tại thời điểm chạy và thao tác lớp theo ý muốn. Ngay cả các trường và phương thức được khai báo là private cũng có thể được thao tác. Nó có vẻ như một kỹ thuật không nên sử dụng vì nó vi phạm đóng gói, một khái niệm quan trọng trong thiết kế hướng đối tượng.
Trong giai đoạn phát triển cấp độ bảng điều khiển quy mô nhỏ, nhà phát triển có thể xác định đầy đủ các đối tượng và mối quan hệ phụ thuộc được sử dụng trong chương trình tại thời điểm biên dịch. Tuy nhiên, trong giai đoạn phát triển quy mô lớn như khung, rất khó để xác định vô số đối tượng và mối quan hệ phụ thuộc. Trong trường hợp này, phản chiếu cho phép bạn tạo lớp động để thiết lập mối quan hệ phụ thuộc.
Ví dụ, nếu bạn nhìn vào Nhà máy Bean của Spring, bạn sẽ thấy rằng chỉ cần gắn chú thích @Controller, @Service và @Repository, Nhà máy Bean sẽ tự động tạo và quản lý các lớp có chú thích tương ứng. Nhà phát triển không bao giờ cung cấp cho Nhà máy Bean lớp đó, điều này là có thể nhờ vào phản chiếu. Khi tìm kiếm các lớp có chú thích tương ứng tại thời điểm chạy, nó tạo thể hiện của lớp đó thông qua phản chiếu, tiêm các trường cần thiết và lưu trữ nó trong Nhà máy Bean.
Tất nhiên, như đã đề cập ở trên, nó vi phạm đóng gói, vì vậy tốt nhất là chỉ sử dụng nó khi thật sự cần thiết.