天天看點

一篇文章弄懂 Java 反射的使用

說到Java

反射

,必須先把 Java 的位元組碼搞明白了,也就是

Class

, 大

Class

在之前的文章中,我們知道了Java的大

Class

就是類的位元組碼,就是一個普通的類,裡面儲存的是類的資訊,還不太明白Java的大

Class

的,可以先看一下之前的文章 一篇文章徹底搞懂Java的大Class到底是什麼

先想一個問題

1. 給我們一個類,我們如何使用?

這還不簡單,通過這個類,建立一個類的對象,再通過這個對象,調用類的方法或者屬性

比如有一個類叫

Student

, 裡面有一個

name

字段和一個

age

字段,還有3個方法, 源碼如下:

package com.model;

public class Student {
    private String name;
    private int age;

    public Student(){
    }

    public Student(String name,int age){
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public void show(){
        System.out.println("name=" + this.name + " age=" + this.age);
    }
}

           

上面的代碼很簡單,應該都能看懂,我們以這個

Student

類來實驗

回到上面的問題:如何使用這個類? ,代碼如下:

//1 建立一個對象
        Student s = new Student();

        //2 調用對象的方法
        s.setName("李雷");
        s.setAge(23);

        //3 列印一下
        s.show();
           

列印的結果下:

name=李雷 age=23

上面就是和

反射

相反的通過正常的方式建立一個類的對象,然後通過對象調用類的方法

其實我們還可以根據類的位元組碼來建立對象,然後調用類的方法

也就是通過某個類的 Class ,來建立對象,然後調用類的方法

2. 如何擷取類的 Class 呢?

3

個方法,以

Student

為例,示範如下:

第一種:通過 Class.forName("com.model.Student") 來擷取Student的 Class

代碼如下:

Class cls = Class.forName("com.model.Student");
           

第二種:通過 Student.class

Class cls = Student.class
           

第三種:通過類的對象來擷取,調用類的對象的 getClass()方法

Student s = new Student();
Class cls = s.getClass()
           

以上就是三種方法擷取一個類的

Class

的方法,必須要牢記,尤其是前 2 個,用的最多

3. 如何通過

Class

來建立對象,進而來調用類的方法或者屬性呢?

  • 第一步:擷取類的 Class 對象
  • 第二步:擷取對應的方法的位元組碼

    Method

    以及 構造函數的位元組碼

    Constructor

    ,或者字段的位元組碼

    Field

  • 第三步:通過

    Constructor

    newInstance()

    方法生成一個類的對象
  • 第四步:通過調用

    Method

    invoke()

    方法完成調用類的代碼

代碼示範如下:

//第一步:擷取Student的 Class 對象,即Student的位元組碼
        Class cls = Class.forName("com.model.Student");

        //第二步:擷取無參的構造方法的位元組碼,當然也可以擷取有參的
        Constructor constructor = cls.getConstructor();

        //第三步:調用構造函數的位元組碼對象的 newInstance 方法建立 Student的對象 obj
        Object obj = constructor.newInstance();

        //第四步:擷取 setName 方法的位元組碼,注意參數傳方法的名字以及方法中參數的位元組碼
        //      擷取了setName的位元組碼 method,調用方法必須要有一個對象,是以上面的obj對象就是用來此處的
        //      一定要傳進行
        Method method = cls.getMethod("setName", String.class);
        method.invoke(obj,"待兔");

        //和上面類似,隻不過這次 getMethod 的第二個參數傳的是 int.class
        //因為第二個參數是int類型
        Method method1 = cls.getMethod("setAge", int.class);
        method1.invoke(obj,23);

        //和上面類似 ,隻不過 show()方法是無參的,是以 getMethod 隻需要傳方法的名字"show" 即可
        Method showMethod = cls.getMethod("show");

        //最後:調用showMethod方法,通過調用showMethod的invoke方法,裡面傳入前面建立的obj對象
        //就達到了調用對象的show方法
        showMethod.invoke(obj);
           

通過上面的代碼示範可以看出,在不知道

Student

類型的情況下,我們隻需要知道

Student

類的全類名(包名+類名)

也就是

com.model.Student

,就可以擷取到

Student

類的

和直接通過

Student s = new Student(); s.show();

這種方法不一樣的是,上面是在運作時通過字元串值知道要運作的類是

com.model.Student

是以,反射就是在運作的時候 ,才知道這個類是什麼,并且可以在運作的時候 ,擷取這個類的完整資訊,并調用對應的方法

4. 常用的反射API

4.1 在反射中,要擷取一個類或調用一個類的方法,我們首先需要擷取到該類的 Class 對象

擷取Class對象有三種方法,上面已經講過,這裡再次貼出來,加深印象

  • 使用 Class.forName 靜态方法,前提是你知道類的全類名

    Class cls = Class.forName("com.model.Student");

    ,其實這種方法就是加載類的
  • 使用類的 .class 方法

    Class cls = Student.class

    不過這種方法,隻适合在編譯時就知道操作的 Class
  • 使用類對象的 getClass() 方法。

    Student s = new Student();

    Class cls = s.getClass()

4.2 擷取所有類的的方法

可以通過 Class對象 getMethods()或者 cls.getDeclaredMethods() 來擷取所有的方法的位元組碼

兩者的差別是:getMethods()擷取的方法包括父類的,getDeclaredMethods() 擷取的是子類的

示範 getMethods()

Method[] methods = cls.getMethods();
for (Method m : methods) {
    System.out.println(m.getName());
}
           

輸出出下:

getName

setName

setAge

show

getAge

wait

wait

wait

equals

toString

hashCode

getClass

notify

notifyAll

可以看到,輸出了很多父類中的方法(Object類中的方法)

再來看一下 getDeclaredMethods() 方法

Method[] methods = cls.getDeclaredMethods();
for (Method m : methods) {
    System.out.println(m.getName());
}
           

輸出如下:

getName

setName

setAge

show

getAge

可以看到,隻有子類自己的方法,并沒有父類的方法

5. 通過反射建立類的對象需要注意的點

上面我們通過

Constructor

對象的

newInstance()

方法,來建立對象

其實還有一種方法,也可以使用

Class

對象的

newInstance()

5.1 第一種:通過 Class 對象

newInstance()

方法

Class cls = Class.forName("com.model.Student");
Student obj = (Student) cls.newInstance();
           

5.2 第二種:通過 Constructor 對象的 newInstance() 方法

//第一步:擷取Student的 Class 對象,即Student的位元組碼
Class cls = Class.forName("com.model.Student");

//第二步:擷取無參的構造方法的位元組碼,當然也可以擷取有參的
Constructor constructor = cls.getConstructor();

//第三步:調用構造函數的位元組碼對象的 newInstance 方法建立 Student的對象 obj
Object obj = constructor.newInstance();
           

::: warning

通過 Constructor 對象建立類對象可以選擇特定構造方法,而通過 Class 對象則隻能使用預設的無參數構造方法。

下面的代碼就調用了一個有參數的構造方法進行了類對象的初始化。

:::

Class cls = Class.forName("com.model.Student");
Constructor constructor = cls.getConstructor(String.class,int.class);
Object obj = constructor.newInstance("tom",23);
           

通過上面的講解,應該對反射的用法有了個大緻的了解了,Class有很多方法,感興趣的可以自己寫個

helloworld

調試一下

不過怎麼說,還是要先弄明白

Class

到底是什麼,知道了

Class

的本質 ,再來看反射,就很容易了

還不太明白的一定要看看下面的文章

一篇文章徹底搞懂Java的大Class到底是什麼