不學(xué)無數(shù)——初識(shí)反射

反射:運(yùn)行時(shí)的類信息

運(yùn)行時(shí)類型信息使得你可以在程序運(yùn)行時(shí)發(fā)現(xiàn)和使用類型信息

1. Class對(duì)象

通過Class對(duì)象可以在運(yùn)行時(shí)發(fā)現(xiàn)一個(gè)對(duì)象完整的類繼承結(jié)構(gòu)

類是程序的一部分,每一個(gè)類都會(huì)有一個(gè)Class對(duì)象。換句話說既每編寫一個(gè)新的類,就會(huì)產(chǎn)生一個(gè)Class對(duì)象。而這些Class對(duì)象信息是保存在我們用javac 類名.java 進(jìn)行編譯時(shí)產(chǎn)生的.class文件中的。為了生成這個(gè)對(duì)象,運(yùn)行這個(gè)程序的java虛擬機(jī)(JVM)會(huì)使用類加載器進(jìn)行加載

1.1 什么是類加載器

Java類加載器(Java Classloader)是Java運(yùn)行時(shí)環(huán)境(Java Runtime Environment)的一部分,負(fù)責(zé)動(dòng)態(tài)加載Java類到Java虛擬機(jī)的內(nèi)存空間中

即類加載器是Java虛擬機(jī)將描述類的數(shù)據(jù),例如類的各種方法,構(gòu)造參數(shù)之類的信息從Class文件加載到內(nèi)存中去,并且對(duì)數(shù)據(jù)進(jìn)行校驗(yàn)、轉(zhuǎn)換解析和初始化,最終形成可以被虛擬機(jī)直接使用的java類型。

所有的類都是在對(duì)其進(jìn)行第一次使用的時(shí)候,動(dòng)態(tài)加載到JVM中的,即和編譯時(shí)需要進(jìn)行連接工作的語言不通,在java中,類型的加載、連接和初始化都是在程序運(yùn)行期間完成的。

類加載器在進(jìn)行加載的時(shí)候會(huì)首先檢查這個(gè)類的Class對(duì)象是否已經(jīng)進(jìn)行了加載,如果沒有加載,那么默認(rèn)的類加載器就會(huì)根據(jù)類名進(jìn)行查找.class文件。通過下面例子,我們可以清楚看出類是在第一次被使用的時(shí)候才會(huì)加載。

static靜態(tài)代碼塊的加載是在類加載的時(shí)候進(jìn)行的

class Tom{
    static {
        System.out.println("Loading Tom");
    }
}

class Jerry{
    static {
        System.out.println("Loading Jerry");
    }
}

class Mary{
    static {
        System.out.println("Loading Mary");
    }
}
public class Demo4 {
    public static void main(String[] args) {
        System.out.println("Inside Main");
        new Tom();
        System.out.println("After Loading Tom");
        new Mary();
        System.out.println("After Loading Mary");
        new Jerry();
    }
}

輸出結(jié)果如下:

Inside Main
Loading Tom
After Loading Tom
Loading Mary
After Loading Mary
Loading Jerry

類加載器在加載類的過程中會(huì)分為三個(gè)階段

  • 加載:加載.class文件的字節(jié)碼文件
  • 連接:為類分配靜態(tài)域,并為變量分配初始值
  • 初始化:會(huì)真正的執(zhí)行類中定義的java程序代碼,既初始化靜態(tài)域中的方法和變量

同一個(gè)類加載器下,一個(gè)類型只會(huì)初始化一次。

1.2 創(chuàng)建Class對(duì)象

  • 根據(jù)對(duì)象的引用.getClass()方法獲取
Tom tom = new Tom();
Class class = tom.getClass();
  • 根據(jù)類名.class獲取
Class class = Tom.class;

  • 根據(jù)Class中的靜態(tài)方法Class.forName,其中的參數(shù)必須為帶包名的類路徑
 Class c = Class.forName("Tom");

通常在反射中創(chuàng)建Class對(duì)象時(shí)使用的第二種方法,因?yàn)榈谝环N已經(jīng)有這個(gè)對(duì)象了,干嘛還需要反射,第三種的話會(huì)有局限性,需要導(dǎo)入所要?jiǎng)?chuàng)建對(duì)象的包路徑。

2. 使用反射

我們在上面獲取到Class對(duì)象,而我們拿到了Class對(duì)象以后就能對(duì)其進(jìn)行操作

2.1 構(gòu)造方法

Class類和javalang.reflect類庫一起對(duì)反射的概念進(jìn)行了支持,該類庫包含了Field,Method以及Constructor類(每個(gè)類都實(shí)現(xiàn)了Member接口).這些類型的對(duì)象是由JVM在運(yùn)行時(shí)創(chuàng)建的,用以表示未知類里所對(duì)應(yīng)的成員信息.這樣就可以用Constructor創(chuàng)建新的對(duì)象。下面演示一下通過反射獲取構(gòu)造方法。

2.1.1 獲取公有構(gòu)造方法

public class A {
    private String a;
    private String b;
    public String c;

    public A(String a, String b) {
        this.a = a;
        this.b = b;
    }

    private A(String a){
        this.a=a;
    }
    public A(){}
   ——————————get.set方法省略
}
Class a=Class.forName("Practice.Day05.A");
Constructor[] constructors = a.getConstructors();
for (Constructor constructor:constructors){
    System.out.println(constructor);
}

此時(shí)我們發(fā)現(xiàn)打印出來的信息如下:

public Practice.Day05.A()
public Practice.Day05.A(java.lang.String,java.lang.String)

2.1.2 獲取所有構(gòu)造方法

我們發(fā)現(xiàn)沒有私有的構(gòu)造函數(shù),因?yàn)?code>getConstructors()方法獲得是public的構(gòu)造函數(shù),而getDeclaredFields()方法獲得是包括public, protected, default,private的構(gòu)造函數(shù),此時(shí)如果我們將代碼改成如下:

Class a=Class.forName("Practice.Day05.A");
Constructor[] constructors = a.getDeclaredConstructors();
for (Constructor constructor:constructors){
    System.out.println(constructor);

}

我們發(fā)現(xiàn)打印的參數(shù)就會(huì)多了一個(gè)private的構(gòu)造函數(shù)

public Practice.Day05.A()
private Practice.Day05.A(java.lang.String)
public Practice.Day05.A(java.lang.String,java.lang.String)

2.1.3 獲得具體類型的構(gòu)造方法

我們在上面都是獲得了一個(gè)構(gòu)造方法的數(shù)組,如果想要獲得具體的構(gòu)造方法的話,那么可以通過傳入構(gòu)造方法的入?yún)⒌念愋?,可以獲得這個(gè)構(gòu)造方法,具體例子如下:

Class a=Class.forName("Practice.Day05.A");
Constructor constructor = a.getConstructor(null);
Constructor stringConstructor = a.getConstructor(String.class,String.class);
Constructor stringPrivateConstructor=a.getDeclaredConstructor(String.class);
System.out.println(constructor);
System.out.println(stringConstructor);
System.out.println(stringPrivateConstructor);

打印的信息如下:

public Practice.Day05.A()
public Practice.Day05.A(java.lang.String,java.lang.String)
private Practice.Day05.A(java.lang.String)

2.1.4 通過構(gòu)造方法實(shí)例化類

我們獲得了Constructor對(duì)象以后,可以通過其中的newInstance方法進(jìn)行實(shí)例化對(duì)象。例子如下:

Class a=Class.forName("Practice.Day05.A");
Constructor constructor = a.getConstructor(null);
Constructor stringConstructor = a.getConstructor(String.class,String.class);
A nullA= (A) constructor.newInstance();
A stringA= (A) stringConstructor.newInstance("BuXueWuShu","BuXueWuShu");
nullA.setA("BuXueWuShu");
System.out.println("nullA:"+nullA.getA());
System.out.println("stringA:"+stringA.getA());

打印信息如下:

nullA:BuXueWuShu
stringA:BuXueWuShu

2.2 成員變量

還是上面的實(shí)體類的例子,其中有私有的兩個(gè)變量是a和b,公有的變量是c。

2.2.1 獲取成員變量

如果類中有屬性的話,兩個(gè)方法都是返回一個(gè)Field數(shù)組.其中getDeclaredFields()方法返回的是所有參數(shù)包括public, protected, default,private,而getFields()只返回public的參數(shù)。例如如下

Class a=Class.forName("Practice.Day05.A");
Field[] allDeclaredFields = a.getDeclaredFields();--獲得所有成員變量
Field[] fields = a.getFields();--只獲得public的成員變量
for (Field field:allDeclaredFields){
    System.out.println(field);
}
System.out.println("-----------------------");
for (Field field:fields){
    System.out.println(field);
}

打印的參數(shù)如下:

private java.lang.String Practice.Day05.A.a
private java.lang.String Practice.Day05.A.b
public java.lang.String Practice.Day05.A.c
-----------------------
public java.lang.String Practice.Day05.A.c

2.2.2 獲得指定成員變量

獲得指定的成員變量其實(shí)和上面的獲取指定的構(gòu)造方法是一樣的。舉例如下:

Class a=Class.forName("Practice.Day05.A");
Field c1 = a.getField("c");
Field a1 = a.getDeclaredField("a");
System.out.println(c1);
System.out.println("---------------------");
System.out.println(a1);

打印參數(shù)如下:

public java.lang.String Practice.Day05.A.c
---------------------
private java.lang.String Practice.Day05.A.a

2.2.3 為成員變量賦值

獲得了Field的對(duì)象后,可以調(diào)用get()set()方法對(duì)某個(gè)對(duì)象中的屬性進(jìn)行取值和賦值的操作。例子如下

Class a=Class.forName("Practice.Day05.A");
Constructor nullClass=a.getDeclaredConstructor(null);
A nullA= (A) nullClass.newInstance();--獲得A的實(shí)例化對(duì)象
Field a1 = a.getDeclaredField("a");
a1.setAccessible(true);--變量a是private的,所以需要解除私有限定
a1.set(nullA,"BuXueWuShu");--為nullA對(duì)象中的變量a進(jìn)行賦值操作
System.out.println("nullA="+a1.get(nullA));--取出nullA對(duì)象中的變量a

打印信息如下:

nullA=BuXueWuShu

注意在對(duì)私有的成員變量進(jìn)行賦值操作時(shí),要解除私有限定,調(diào)用setAccessible()方法,賦值為true

2.3 成員方法

2.3.1 獲取成員方法

通過獲得的Class對(duì)象,調(diào)用它的getDeclaredMethods()getMethods()方法可以獲得類中的方法的信息。例如有以下的一個(gè)類。


public class TestMethod {

    private String playName;

    public void show(String playName){
        System.out.println("I Love "+playName);
    }

    private String returnPlayName(){
        return playName;
    }
}

做如下的調(diào)用:

Class a=Class.forName("Practice.Day05.TestMethod");
Method[] allDeclaredMethods = a.getDeclaredMethods();
Method[] methods = a.getMethods();
for (Method method:allDeclaredMethods){
    System.out.println(method);
}
System.out.println("-----------------");
for (Method method:methods){
    System.out.println(method);
}

可以發(fā)現(xiàn)打印如下的信息:

public void Practice.Day05.TestMethod.show(java.lang.String)
private java.lang.String Practice.Day05.TestMethod.returnPlayName()
-----------------
public void Practice.Day05.TestMethod.show(java.lang.String)
public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
public final void java.lang.Object.wait() throws java.lang.InterruptedException
public boolean java.lang.Object.equals(java.lang.Object)
public java.lang.String java.lang.Object.toString()
public native int java.lang.Object.hashCode()
public final native java.lang.Class java.lang.Object.getClass()
public final native void java.lang.Object.notify()
public final native void java.lang.Object.notifyAll()

getDeclaredMethods()方法會(huì)獲得自身和實(shí)現(xiàn)的接口中所有的方法,但是不會(huì)獲得繼承來的方法,getMethods()方法會(huì)獲得所有的無論是實(shí)現(xiàn)的接口還是繼承來的public的方法

2.3.2 獲得指定的方法

通過方法名和方法中的參數(shù)類型就可以獲得指定的方法

Class a=Class.forName("Practice.Day05.TestMethod");
Method show = a.getDeclaredMethod("show", String.class);
System.out.println(show);

打印信息如下:

public void Practice.Day05.TestMethod.show(java.lang.String)

2.3.3 使用方法

可以通過調(diào)用Method對(duì)象的invoke()方法進(jìn)行調(diào)用方法。例子如下:

Class a=Class.forName("Practice.Day05.TestMethod");
Constructor nullClass=a.getDeclaredConstructor(null);
TestMethod nullTestMethod= (TestMethod) nullClass.newInstance();
Method show = a.getDeclaredMethod("show",String.class);
show.invoke(nullTestMethod,"BasketBall");

打印參數(shù)如下:

I Love BasketBall

如果調(diào)用的是private的方法,那么在使用invoke()方法之前要先解除私有限定,即調(diào)用setAccessible()方法,賦值為true

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容