不學無數——初識反射

反射:運行時的類信息

運行時類型信息使得你能夠在程序運行時發現和使用類型信息java

1. Class對象

經過Class對象能夠在運行時發現一個對象完整的類繼承結構數組

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

1.1 什麼是類加載器

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

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

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

類加載器在進行加載的時候會首先檢查這個類的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

相關文章
相關標籤/搜索