Sử dụng Java Reflection để thao tác với constructor, fields, method

Java reflection là một kỹ thuật cho phép kiểm tra hoặc sửa đổi các thuộc tính, method trong class, interface tại thời điểm runtime. Ngoài ra chúng ta cũng có thể sử dụng reflection để khởi tạo các object, thực hiện các lời gọi hàm hoặc thậm chí có thể truy cập hoặc thay đổi giá trị của các thuộc tính trong một class.

Trong bài viết này chúng ta sẽ cùng nhau tìm hiểu cách sử dụng Java reflection để thao tác với constructor, trường dữ liệu – field, và method trong Java.

Chuẩn bị

Trước khi tìm hiểu cách sử dụng Java reflection để thao tác với constructor, field và constructor chúng ta cần chuẩn bị một số thứ dùng cho các ví dụ ở các phần sau.

public interface Eating {
    String eats();
}

Animal abstract class implement từ Eating interface.

public abstract class Animal implements Eating {

    public static String CATEGORY = "domestic";
    private String name;

    protected abstract String getSound();

    public Animal(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

Cuối cùng là Bird class extends từ Animal class

public class Bird extends Animal {
    private boolean walks;

    public Bird() {
        super("bird");
    }

    public Bird(String name, boolean walks) {
        super(name);
        setWalks(walks);
    }

    public boolean isWalks() {
        return walks;
    }

    public void setWalks(boolean walks) {
        this.walks = walks;
    }

    public Bird(String name) {
        super(name);
    }

    public boolean walks() {
        return walks;
    }

    @Override
    public String eats() {
        return null;
    }

    @Override
    protected String getSound() {
        return null;
    }
}

Thao tác với constructor

Với Java reflection có ta có thể kiểm tra các constructor của một class bất kỳ tại thời điểm runtime. Hoặc thậm chí chúng ta có thể khởi tạo một object của class này với java.lang.reflect.Constructor class.

Việc đơn giản nhất chúng ta có thể thao tác đầu tiên đó là kiểm tra tất cả các constructor có trong một class – trong ví dụ này là Bird class.

import java.lang.reflect.Constructor;

public class Main {

    public static void main(String[] args) throws ClassNotFoundException {
        Class<?> birdClass = Class.forName("com.deft.reflection.Bird");
        Constructor<?>[] constructors = birdClass.getConstructors();
        System.out.println("Length: " + constructors.length);
    }
}

Output: Length: 3

Bây giờ chúng ta có thể lấy ra bất kỳ một constructor dựa vào tham số đầu vào

import java.lang.reflect.Constructor;

public class Main {

    public static void main(String[] args) throws NoSuchMethodException, ClassNotFoundException {
        Class<?> birdClass = Class.forName("com.deft.reflection.Bird");

        Constructor<?> cons1 = birdClass.getConstructor();
        Constructor<?> cons2 = birdClass.getConstructor(String.class);
        Constructor<?> cons3 = birdClass.getConstructor(String.class, boolean.class);
    }
}

Chúng ta cần lưu ý, nếu getConstructor(parameters) không tìm thấy được một constructor tương ứng nó sẽ quăng NoSuchMethodException.

Sau khi đã lấy được các constructor instance về chúng ta có thể khởi tạo các Bird object từ các constructors instance như sau

import java.lang.reflect.Constructor;

public class Main {

    public static void main(String[] args) throws Exception {
        Class<?> birdClass = Class.forName("com.deft.reflection.Bird");

        Constructor<?> cons1 = birdClass.getConstructor();
        Constructor<?> cons2 = birdClass.getConstructor(String.class);
        Constructor<?> cons3 = birdClass.getConstructor(String.class, boolean.class);
        Bird bird1 = (Bird) cons1.newInstance();
        Bird bird2 = (Bird) cons2.newInstance("Weaver bird");
        Bird bird3 = (Bird) cons3.newInstance("dove", true);

        System.out.println("Bird 1: " + bird1.getName());
        System.out.println("Bird 2: " + bird2.getName());
        System.out.println("Bird 3: " + bird3.getName());
    }
}

Output

Bird 1: bird
Bird 2: Weaver bird
Bird 3: dove

Thao tác với field

Có hai phương thức chính được sử dụng để kiểm tra các field trong một class tại thời điểm runtime: getFields()getField(fieldName).

Method getFields() trả về tất cả các public field của một class bao gồm cả các public field của class cha.

import java.lang.reflect.Field;

public class Main {

    public static void main(String[] args) throws Exception {
        Class<?> birdClass = Class.forName("com.deft.reflection.Bird");

        Field[] fields = birdClass.getFields();
        System.out.println("Length: " + fields.length);
        System.out.println("Field name: " + fields[0].getName());
    }
}

Output

Length: 1
Field name: CATEGORY

Chúng ta có thể thấy rằng field name không được trả về vì nó là private field được định nghĩa trong Animal. 

Method getField() dùng để lấy một Field instance trực tiếp dựa vào tên của biến được truyền vào

import java.lang.reflect.Field;

public class Main {

    public static void main(String[] args) throws Exception {
        Class<?> birdClass = Class.forName("com.deft.reflection.Bird");

        Field field = birdClass.getField("CATEGORY");
        System.out.println("Field name: " + field.getName());
    }
}

Output: Field name: CATEGORY

Chúng ta không thể truy cập được các private field được khai báo trong class cha, nhưng đối với các private field được khai báo trong class con thì chúng ta vẫn có thể truy cập được thông qua getDeclaredFields() method

import java.lang.reflect.Field;

public class Main {

    public static void main(String[] args) throws Exception {
        Class<?> birdClass = Class.forName("com.deft.reflection.Bird");
        Field[] fields = birdClass.getDeclaredFields();
        System.out.println("Length: " + fields.length);
        System.out.println(fields[0].getName());
    }
}

Output: 

Length: 1
walks

Tương tự chúng ta có thể sử dụng getDeclaredField(name) để lấy một field bất kỳ trong một class bao gồm private.

import java.lang.reflect.Field;

public class Main {

    public static void main(String[] args) throws Exception {
        Class<?> birdClass = Class.forName("com.deft.reflection.Bird");
        Field field = birdClass.getDeclaredField("walks");
        System.out.println(field.getName());
    }
}

Output: walks

Ngoài ra để biết thông tin như kiểu dữ liệu của field thì chúng ta có thể sử dụng Field#getType().

public class Main {

    public static void main(String[] args) throws Exception {
        Class<?> birdClass = Class.forName("com.deft.reflection.Bird");
        Field field = birdClass.getDeclaredField("walks");
        System.out.println("Type: " + field.getType());
    }
}

Output: Type: boolean

Tiếp theo là phần hay nhất chúng ta có thể sử dụng reflection để truy cập giá trị của các field kể cả private hoặc thậm chí là chỉnh sửa giá trị của chúng. 

public class Main {

    public static void main(String[] args) throws Exception {
        Class<?> birdClass = Class.forName("com.deft.reflection.Bird");
        Bird bird = (Bird) birdClass.getConstructor().newInstance();
        Field field = birdClass.getDeclaredField("walks");
        field.setAccessible(true);
        System.out.println("Before");
        System.out.println("Reflection get: " + field.getBoolean(bird));
        System.out.println("Instance get: " + bird.walks());

        field.set(bird, true);

        System.out.println("After");

        System.out.println("Reflection get: " + field.getBoolean(bird));
        System.out.println("Instance get: " + bird.walks());
    }
}

Trong đó, điểm quan trọng mà chúng ta cần lưu ý đó là việc sử dụng field.setAccessible(true) sẽ giúp chúng ta có thể truy cập chỉnh sửa field này một cách thoải mái.

Một lưu ý nữa với public static field thì chúng ta không cần phải dùng đến instance để truy cập vào chúng chúng ta chỉ có thể chuyển null vào hàm field.get() để lấy giá trị mặc định.

 

public class Main {

    public static void main(String[] args) throws Exception {
        Class<?> birdClass = Class.forName("com.deft.reflection.Bird");
        Field field = birdClass.getField("CATEGORY");
        field.setAccessible(true);
        System.out.println(field.get(null));
    }
}

Output: domestic

Thao tác Method

Với Java reflection chúng ta không chỉ lấy được tên các method mà còn có gọi thực thi những method này tại thời điểm runtime. 

Cũng giống như các field, có 2 method chính mà chúng ta sử dụng để truy xuất các method của class là getMethods() trả về một mảng tất cả các public method của class hiện tại và class cha của nó.

public class Main {

    public static void main(String[] args) throws Exception {
        Class<?> birdClass = Class.forName("com.deft.reflection.Bird");
        Method[] methods = birdClass.getMethods();
        System.out.println("length: " + methods.length);
        System.out.println("Method list");
        for(Method method : methods) {
            System.out.println(method.getName());
        }
    }
}

Output

length: 15
Method list
eats
setWalks
walks
isWalks
getName
setName
wait
wait
wait
equals
toString
hashCode
getClass
notify
notifyAll

Nếu chỉ quan tâm đến các public method thì chúng ta phải sử dụng phương thức getDeclaredMethods()

public class Main {

    public static void main(String[] args) throws Exception {
        Class<?> birdClass = Class.forName("com.deft.reflection.Bird");
        Method[] methods = birdClass.getDeclaredMethods();
        System.out.println("length: " + methods.length);
        System.out.println("Method list");
        for(Method method : methods) {
            System.out.println(method.getName());
        }
    }
}

Output

length: 5
Method list
isWalks
walks
getSound
setWalks
eats

Tiếp theo chúng ta sẽ dùng Java reflection để gọi một method trong Bird method ngay tại thời điểm runtime

public class Main {

    public static void main(String[] args) throws Exception {
        Class<?> birdClass = Class.forName("com.deft.reflection.Bird");
        Bird bird = (Bird) birdClass.getConstructor().newInstance();
        Method setWalksMethod = birdClass.getDeclaredMethod("setWalks", boolean.class);
        Method walksMethod = birdClass.getDeclaredMethod("walks");
        boolean walks = (boolean) walksMethod.invoke(bird);
        System.out.println("walks-reflection: " + walks);
        System.out.println("walks-instance+ " + bird.walks());

        setWalksMethod.invoke(bird, true);
        System.out.println("----------------------------------------------");
        boolean walks2 = (boolean) walksMethod.invoke(bird);
        System.out.println("walks-reflection: " + walks2);
        System.out.println("walks-instance+ " + bird.walks());
    }
}

Output

walks-reflection: false
walks-instance+ false
----------------------------------------------
walks-reflection: true
walks-instance+ true

Tóm lược

Qua bài viết này chúng ta thấy được sức mạnh của Java reflection, có thể dùng để truy cập và thậm chí là thực hiện lời gọi hàm, khởi tạo đối tượng tại thời điểm runtime. Chúng ta có thể thấy rằng việc sử dụng Java reflection có thể phá vỡ mọi quy tắc trong lập trình hướng đối tượng nên nó thường không được khuyến khích sử dụng.

Nguồn: Baeldung

0 0 votes
Article Rating
Subscribe
Notify of
guest
0 Comments
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x