不學無數——初識反射

反射:運行時的類信息

運行時類型信息使得你可以在程序運行時發現和使用類型信息

1. Class對象

通過Class對象可以在運行時發現一個對象完整的類繼承結構

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

1.1 什么是類加載器

Java類加載器(Java Classloader)是Java運行時環境(Java Runtime Environment)的一部分,負責動態加載Java類到Java虛擬機的內存空間中

即類加載器是Java虛擬機將描述類的數據,例如類的各種方法,構造參數之類的信息從Class文件加載到內存中去,并且對數據進行校驗、轉換解析和初始化,最終形成可以被虛擬機直接使用的java類型。

所有的類都是在對其進行第一次使用的時候,動態加載到JVM中的,即和編譯時需要進行連接工作的語言不通,在java中,類型的加載、連接和初始化都是在程序運行期間完成的。

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

static靜態代碼塊的加載是在類加載的時候進行的

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

輸出結果如下:

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

類加載器在加載類的過程中會分為三個階段

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

同一個類加載器下,一個類型只會初始化一次。

1.2 創建Class對象

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

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

通常在反射中創建Class對象時使用的第二種方法,因為第一種已經有這個對象了,干嘛還需要反射,第三種的話會有局限性,需要導入所要創建對象的包路徑。

2. 使用反射

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

2.1 構造方法

Class類和javalang.reflect類庫一起對反射的概念進行了支持,該類庫包含了FieldMethod以及Constructor類(每個類都實現了Member接口).這些類型的對象是由JVM在運行時創建的,用以表示未知類里所對應的成員信息.這樣就可以用Constructor創建新的對象。下面演示一下通過反射獲取構造方法。

2.1.1 獲取公有構造方法

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

此時我們發現打印出來的信息如下:

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

2.1.2 獲取所有構造方法

我們發現沒有私有的構造函數,因為getConstructors()方法獲得是public的構造函數,而getDeclaredFields()方法獲得是包括public, protected, default,private的構造函數,此時如果我們將代碼改成如下:

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

}

我們發現打印的參數就會多了一個private的構造函數

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

2.1.3 獲得具體類型的構造方法

我們在上面都是獲得了一個構造方法的數組,如果想要獲得具體的構造方法的話,那么可以通過傳入構造方法的入參的類型,可以獲得這個構造方法,具體例子如下:

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 通過構造方法實例化類

我們獲得了Constructor對象以后,可以通過其中的newInstance方法進行實例化對象。例子如下:

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 成員變量

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

2.2.1 獲取成員變量

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

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

打印的參數如下:

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 獲得指定成員變量

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

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

打印參數如下:

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

2.2.3 為成員變量賦值

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

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

打印信息如下:

nullA=BuXueWuShu

注意在對私有的成員變量進行賦值操作時,要解除私有限定,調用setAccessible()方法,賦值為true

2.3 成員方法

2.3.1 獲取成員方法

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


public class TestMethod {

    private String playName;

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

    private String returnPlayName(){
        return playName;
    }
}

做如下的調用:

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

可以發現打印如下的信息:

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()方法會獲得自身和實現的接口中所有的方法,但是不會獲得繼承來的方法,getMethods()方法會獲得所有的無論是實現的接口還是繼承來的public的方法

2.3.2 獲得指定的方法

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

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 使用方法

可以通過調用Method對象的invoke()方法進行調用方法。例子如下:

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");

打印參數如下:

I Love BasketBall

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

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,563評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,694評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,672評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,965評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,690評論 6 413
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,019評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,013評論 3 449
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,188評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,718評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,438評論 3 360
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,667評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,149評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,845評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,252評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,590評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,384評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,635評論 2 380

推薦閱讀更多精彩內容