Reflection API để làm gì?

Cơ chế phản chiếu của Java cho phép nhà phát triển thực hiện các thay đổi và lấy thông tin về các lớp, giao diện, trường và phương thức trong thời gian chạy mà không cần biết tên của chúng.

Reflection API cũng cho phép bạn tạo đối tượng mới, gọi phương thức và nhận hoặc đặt giá trị trường.

Hãy lập danh sách mọi thứ bạn có thể làm bằng cách sử dụng sự phản chiếu:

  • Xác định/xác định lớp của một đối tượng
  • Nhận thông tin về công cụ sửa đổi lớp, trường, phương thức, hằng số, hàm tạo và siêu lớp
  • Tìm ra phương thức nào thuộc về (các) giao diện đã triển khai
  • Tạo một thể hiện của một lớp không biết tên lớp cho đến khi chương trình được thực thi
  • Nhận và đặt giá trị của trường đối tượng theo tên
  • Gọi một phương thức cá thể theo tên

Hầu như tất cả các công nghệ Java hiện đại đều sử dụng sự phản chiếu. Nó làm cơ sở cho hầu hết các khung và thư viện Java/Java EE ngày nay, ví dụ:

  • Các khung công tác mùa xuân để xây dựng các ứng dụng web
  • khung thử nghiệm JUnit

Nếu bạn chưa bao giờ gặp những cơ chế này trước đây, có lẽ bạn đang hỏi tại sao tất cả những điều này lại cần thiết. Câu trả lời khá đơn giản nhưng cũng rất mơ hồ: sự phản chiếu làm tăng đáng kể tính linh hoạt và khả năng tùy chỉnh ứng dụng và mã của chúng ta.

Nhưng luôn có những ưu và nhược điểm. Vì vậy, hãy đề cập đến một vài khuyết điểm:

  • Vi phạm bảo mật ứng dụng. Phản ánh cho phép chúng tôi truy cập mã mà chúng tôi không nên (vi phạm đóng gói).
  • Hạn chế bảo mật. Phản ánh yêu cầu quyền thời gian chạy không có sẵn cho các hệ thống đang chạy trình quản lý bảo mật.
  • Hiệu năng thấp. Phản ánh trong Java xác định các loại động bằng cách quét đường dẫn lớp để tìm lớp cần tải. Điều này làm giảm hiệu suất của chương trình.
  • Khó bảo trì. Mã sử ​​dụng sự phản chiếu rất khó đọc và gỡ lỗi. Nó ít linh hoạt hơn và khó bảo trì hơn.

Làm việc với các lớp bằng Reflection API

Tất cả các hoạt động phản ánh bắt đầu với một đối tượng java.lang.Class . Đối với mỗi loại đối tượng, một thể hiện bất biến của java.lang.Class được tạo. Nó cung cấp các phương thức để nhận các thuộc tính đối tượng, tạo các đối tượng mới và gọi các phương thức.

Hãy xem danh sách các phương thức cơ bản để làm việc với java.lang.Class :

Phương pháp Hoạt động
Chuỗi getName(); Trả về tên của lớp
int getModifiers(); Trả về công cụ sửa đổi quyền truy cập
Gói getPackage(); Trả về thông tin về một gói
Lớp getSuperclass(); Trả về thông tin về lớp cha
Lớp[] getInterfaces(); Trả về một mảng giao diện
Constructor[] getConstructors(); Trả về thông tin về các hàm tạo của lớp
Trường[] getFields(); Trả về các trường của một lớp
Trường getField(String fieldName); Trả về một trường cụ thể của một lớp theo tên
Phương thức[] getMethods(); Trả về một mảng các phương thức

Đây là những phương pháp quan trọng nhất để lấy dữ liệu về các lớp, giao diện, trường và phương thức. Ngoài ra còn có các phương thức cho phép bạn nhận hoặc đặt giá trị trường và truy cập các trường riêng tư . Chúng ta sẽ xem xét chúng sau.

Ngay bây giờ chúng ta sẽ nói về việc lấy java.lang.Class . Chúng tôi có ba cách để làm điều này.

1. Sử dụng Class.forName

Trong một ứng dụng đang chạy, bạn phải sử dụng phương thức forName(String className) để lấy một lớp.

Đoạn mã này trình bày cách chúng ta có thể tạo các lớp bằng sự phản chiếu. Hãy tạo một lớp Person mà chúng ta có thể làm việc với:

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;
    }
}

Và phần thứ hai trong ví dụ của chúng ta là đoạn mã sử dụng sự phản chiếu:

public class TestReflection {
    public static void main(String[] args) {
        try {
            Class<?> aClass = Class.forName("com.company.Person");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

Cách tiếp cận này có thể thực hiện được nếu biết tên đầy đủ của lớp. Sau đó, bạn có thể lấy lớp tương ứng bằng phương thức tĩnh Class.forName() . Phương pháp này không thể được sử dụng cho các loại nguyên thủy.

2. Sử dụng .class

Nếu một loại có sẵn nhưng không có phiên bản nào của nó, thì bạn có thể lấy loại đó bằng cách thêm .class vào tên loại. Đây là cách dễ nhất để có được lớp kiểu nguyên thủy.

Class aClass = Person.class;

3. Sử dụng .getClass()

Nếu một đối tượng có sẵn, thì cách dễ nhất để lấy một lớp là gọi object.getClass() .

Person person = new Person();
Class aClass = person.getClass();

Sự khác biệt giữa hai cách tiếp cận cuối cùng là gì?

Sử dụng A.class nếu bạn biết bạn quan tâm đến đối tượng lớp nào tại thời điểm viết mã. Nếu không có phiên bản nào, thì bạn nên sử dụng .class .

Lấy các phương thức của một lớp

Hãy xem các phương thức trả về các phương thức của lớp chúng ta: getDeclaredMethods()getMethods() .

getDeclaredMethods() trả về một mảng chứa các đối tượng Phương thức cho tất cả các phương thức đã khai báo của lớp hoặc giao diện được đại diện bởi đối tượng Lớp, bao gồm các phương thức công khai, riêng tư, mặc định và được bảo vệ, nhưng không phải các phương thức kế thừa.

getMethods() trả về một mảng chứa các đối tượng Phương thức cho tất cả các phương thức công khai của lớp hoặc giao diện được đại diện bởi đối tượng Lớp — những phương thức được khai báo bởi lớp hoặc giao diện, cũng như những phương thức được kế thừa từ các siêu lớp và siêu giao diện.

Chúng ta hãy xem cách mỗi người trong số họ làm việc.

Hãy bắt đầu với getDeclaredMethods() . Để một lần nữa giúp chúng ta hiểu sự khác biệt giữa hai phương thức, dưới đây chúng ta sẽ làm việc với lớp trừu tượng Numbers . Hãy viết một phương thức tĩnh sẽ chuyển mảng Phương thức của chúng ta thành 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());
    }
}

Đây là kết quả của việc chạy mã này:

byteValue
shortValue
intValue
longValue
float floatValue;
gấp đôigiá trị

Đây là những phương thức được khai báo bên trong lớp Number . getMethods() trả về cái gì ? Hãy thay đổi hai dòng trong ví dụ:

final Method[] methods = Number.class.getMethods();
List<String> actualMethodNames = getMethodNames(methods);

Làm điều này, chúng ta sẽ thấy tập hợp các phương thức sau:

byteValue
shortValue
intValue
longValue
float floatValue;
doubleValue
chờ
đợi chờ
đợi
bằng với Chuỗi băm Mã
getClass thông báo cho tất cả



Vì tất cả các lớp đều kế thừa Object nên phương thức của chúng ta cũng trả về các phương thức công khai của lớp Object .

Lấy các trường của một lớp

Các phương thức getFieldsgetDeclaredFields được sử dụng để lấy các trường của một lớp. Ví dụ, hãy xem lớp LocalDateTime . Chúng tôi sẽ viết lại mã của chúng tôi:

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());
    }
}

Kết quả của việc thực thi mã này, chúng tôi nhận được tập hợp các trường có trong lớp LocalDateTime.

ngày giờ MIN
MAX
serialVersionUID

Tương tự với việc kiểm tra các phương thức trước đây của chúng ta, hãy xem điều gì sẽ xảy ra nếu chúng ta thay đổi mã một chút:

final Field[] fields = LocalDateTime.class.getFields();
List<String> actualFieldNames = getFieldNames(fields);

Đầu ra:

TỐI
ĐA

Bây giờ hãy tìm ra sự khác biệt giữa các phương pháp này.

Phương thức getDeclaredFields trả về một mảng các đối tượng Trường cho tất cả các trường được khai báo bởi lớp hoặc giao diện được đại diện bởi điều nàyLớp họcsự vật.

Phương thức getFields trả về một mảng các đối tượng Trường cho tất cả các trường công khai của lớp hoặc giao diện được đại diện bởiLớp họcsự vật.

Bây giờ hãy xem bên trong LocalDateTime .

của lớpTỐI THIỂUTỐI ĐAcác trường là công khai, có nghĩa là chúng sẽ hiển thị thông qua phương thức getFields . Ngược lại, cácngày,thời gian,serialVersionUIDphương thức có công cụ sửa đổi riêng , có nghĩa là chúng sẽ không hiển thị thông qua phương thức getFields , nhưng chúng ta có thể lấy chúng bằng cách sử dụng getDeclaredFields . Đây là cách chúng ta có thể truy cập các đối tượng Trường cho các trường riêng tư .

Mô tả các phương pháp khác

Bây giờ là lúc nói về một số phương thức của lớp Class , cụ thể là:

Phương pháp Hoạt động
getModifiers Lấy các công cụ sửa đổi cho lớp của chúng tôi
nhận gói Lấy gói chứa lớp của chúng tôi
getSuperclass Lấy lớp cha
getInterfaces Nhận một mảng các giao diện được triển khai bởi một lớp
getName Lấy tên lớp đủ điều kiện
getSimpleName Lấy tên của một lớp

getModifier()

Công cụ sửa đổi có thể được truy cập bằng cách sử dụng mộtLớp họcsự vật.

Công cụ sửa đổi là các từ khóa như public , static , interface , v.v. Chúng tôi nhận các công cụ sửa đổi bằng phương thức getModifiers() :

Class<Person> personClass = Person.class;
int classModifiers = personClass.getModifiers();

Mã này đặt giá trị của mộtintbiến đó là một trường bit. Mỗi công cụ sửa đổi truy cập có thể được bật hoặc tắt bằng cách đặt hoặc xóa bit tương ứng. Chúng ta có thể kiểm tra các công cụ sửa đổi bằng cách sử dụng các phương thức trong lớp 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);
    }
}

Nhớ lại tuyên bố của Người của chúng tôi trông như thế nào:

public class Person {}

Chúng tôi nhận được đầu ra sau:

Công cụ sửa đổi lớp: 1
Là công khai: đúng
Là tĩnh: sai
Là cuối cùng: sai
Là trừu tượng: sai
Là giao diện: sai

Nếu chúng ta tạo lớp trừu tượng, thì chúng ta có:

public abstract class Person {}

và đầu ra này:

Công cụ sửa đổi lớp: 1025
Là công khai: đúng
Là tĩnh: sai
Là cuối cùng: sai Là
trừu tượng: đúng
Là giao diện: sai

Chúng tôi đã thay đổi công cụ sửa đổi truy cập, điều đó có nghĩa là chúng tôi cũng đã thay đổi dữ liệu được trả về thông qua các phương thức tĩnh của lớp Công cụ sửa đổi .

getPackage()

Chỉ biết một lớp, chúng ta có thể lấy thông tin về gói của nó:

Class<Person> personClass = Person.class;
final Package aPackage = personClass.getPackage();
System.out.println(aPackage.getName());

getSuperclass()

Nếu chúng ta có một đối tượng Lớp, thì chúng ta có thể truy cập lớp cha của nó:

public static void main(String[] args) {
    Class<Person> personClass = Person.class;
    final Class<? super Person> superclass = personClass.getSuperclass();
    System.out.println(superclass);
}

Chúng tôi nhận được lớp Object nổi tiếng :

class java.lang.Object

Nhưng nếu lớp của chúng ta có một lớp cha khác, thì chúng ta sẽ thấy nó thay thế:

package com.company;

class Human {
    // Some info
}

public class Person extends Human {
    private int age;
    private String name;

    // Some info
}

Ở đây chúng tôi nhận được lớp cha mẹ của chúng tôi:

class com.company.Human

getInterface()

Đây là cách chúng ta có thể lấy danh sách các giao diện được triển khai bởi lớp:

public static void main(String[] args) {
    Class<Person> personClass = Person.class;
    final Class<?>[] interfaces = personClass.getInterfaces();
    System.out.println(Arrays.toString(interfaces));
}

Và đừng quên thay đổi lớp Person của chúng ta :

public class Person implements Serializable {}

Đầu ra:

[giao diện java.io.Serializable]

Một lớp có thể thực hiện nhiều giao diện. Đó là lý do tại sao chúng tôi nhận được một loạt cácLớp họccác đối tượng. Trong Java Reflection API, các giao diện cũng được đại diện bởiLớp họccác đối tượng.

Xin lưu ý: Phương thức này chỉ trả về các giao diện được triển khai bởi lớp đã chỉ định, không phải lớp cha của nó. Để có danh sách đầy đủ các giao diện do lớp triển khai, bạn cần tham khảo cả lớp hiện tại và tất cả tổ tiên của nó trong chuỗi thừa kế.

getName() & getSimpleName() & getCanonicalName()

Hãy viết một ví dụ liên quan đến một lớp nguyên thủy, lớp lồng nhau, lớp ẩn danh và lớp Chuỗi :

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());
    }
}

Kết quả của chương trình của chúng tôi:

lớp int (nguyên thủy):
getName()): int
getCanonicalName()): int
getSimpleName()): int
getTypeName(): int

String.class (lớp thông thường):
getName()): java.lang.String
getCanonicalName() ): java.lang.String
getSimpleName()): String
getTypeName(): java.lang.String

java.util.HashMap.SimpleEntry.class (lớp lồng nhau):
getName()): java.util.AbstractMap$SimpleEntry
getCanonicalName( )): java.util.AbstractMap.SimpleEntry
getSimpleName()): SimpleEntry
getTypeName(): java.util.AbstractMap$SimpleEntry

new java.io.Serializable(){}.getClass() (lớp bên trong ẩn danh):
getName() ): TestReflection$1
getCanonicalName()): null
getSimpleName()):
getTypeName(): TestReflection$1

Bây giờ hãy phân tích đầu ra của chương trình của chúng tôi:

  • getName() trả về tên của thực thể.

  • getCanonicalName() trả về tên chuẩn của lớp cơ sở, như được định nghĩa bởi Đặc tả ngôn ngữ Java. Trả về null nếu lớp cơ sở không có tên chính tắc (nghĩa là nếu nó là lớp cục bộ hoặc lớp ẩn danh hoặc một mảng có loại phần tử không có tên chính tắc).

  • getSimpleName() trả về tên đơn giản của lớp cơ sở như được chỉ định trong mã nguồn. Trả về một chuỗi rỗng nếu lớp cơ sở ẩn danh.

  • getTypeName() trả về một chuỗi thông tin cho tên của loại này.