天天看點

Java反射詳解Java反射詳解

Java反射詳解

原文位址:反射詳解

為什麼單獨又寫一篇呢。。。。因為到後面架構時,我發現自己有點基礎不牢,而且排版好像也有點問題(不知道為什麼從本地copy到部落格的md編輯器後有些地方給我變成代碼塊了)。。。是以單獨寫一篇詳細點的。

Class類

閱讀時請注意大小寫

在Java中用來表示運作時類型資訊的對應類就是Class類,Class類也是一個實實在在的類,存在于JDK的

java.lang

包中。java程式編譯生成

.class

檔案,

.class

裡面的内容就是你編譯的

.java

檔案的内容,不過是被編譯為位元組形式,給JVM檢視的。而用來描述

.class

檔案的類,就是

Class

Class類被建立後的對象就是Class對象。實際上在Java中每個類都有一個Class對象,每當我們編寫并且編譯一個新建立的類就會産生一個對應Class對象并且這個Class對象會被儲存在同名.class檔案裡,編譯後的位元組碼檔案儲存的就是Class對象,那為什麼需要這樣一個Class對象呢?是這樣的,當我們new一個新對象或者引用靜态成員變量時,Java虛拟機(JVM)中的類加載器子系統會将對應Class對象加載到JVM中,然後JVM再根據這個類型資訊相關的Class對象建立我們需要執行個體對象或者提供靜态變量的引用值。需要特别注意的是,手動編寫的每個class類,無論建立多少個執行個體對象,在JVM中都隻有一個Class對象,即在記憶體中每個類有且隻有一個相對應的Class對象。而且Class對象隻能由JVM來進行建立和銷毀。

另外需要知道的一點是

在JVM建立的Class類型的對象中

成員變量被封裝為Field對象

構造方法被封裝為Constructor對象

成員方法被封裝為Method對象

其實在這裡我初學時就有些疑惑,既然Class類是按照我自己定義的類一起建立的,而且跟我自己定義的類是一模一樣的。當我需要調用裡面的方法時,我可以使用自己定義的類來調用啊。隻不過要把方法定義為靜态方法罷了。

但是,我後面想到一個問題,這是面對的普通的方法,那麼在最初的構造方法,我要怎麼來調用呢。也就是說,我怎麼通過類來調用構造方法。

上面是我們構造一個A類對象時需要進行的一步。衆所周知,對象是能夠調用方法的,那麼我如何通過對象a來調用A類的構造方法?

調用不到,對吧。因為a本來就是由構造方法構造出來的,a根本就調用不到構造方法。而且,再進一步想,A這個類也調用不到自己的構造方法。

那麼,如果,我要在某些情況下,使用到A的構造方法,或者說,将A的構造方法作為一個函數的參數傳遞,那該怎麼辦?

這就需要用到下面将寫的反射技術,Class類也是反射的基礎

類的加載

第一次使用類的資訊時,

.class

位元組碼檔案會被加載到記憶體當中,存儲在方法區中。

JVM會為加載到方法區的

.class

檔案建立一個Class類型對象,該對象儲存在堆記憶體中,等同于堆記憶體中的Class類型的對象,指向了方法區中的

.class

檔案

一個類隻會被加載一次,是以Class類型的對象隻有一個,且任意類型都有對應的Class類型的對象

反射

那麼,什麼叫反射呢?我這裡用我的了解來講一下,反射就是通過Class類來擷取

.class

檔案的内容,因為

.class

的内容記載了類的所有屬性(成員變量,構造方法,成員方法等)。

當然,具體的定義。。。可以去檢視官方文檔

擷取Class對象

  1. 通過

    java.lang.Object

    類的成員方法

    public Class<?> getClass()

    Person p = new Person();
    Class c1 = p.getClass();
    System.out.println(c1);
               
  2. 通過class屬性
    Class<Person> c2 = Person.class;
    System.out.println(c2);
               
  3. 通過

    java.lang.Class

    類的靜态方法

    public static Class<?> forName(String className)

    : 擷取方法參數指定類名對應的Class類型的對象(使用得最多,因為不需要知道具體的類的名字)
    Class<?> c3 = Class.forName("domain.Person");
    System.out.println(c3);
               

總結一下

擷取class對象方式 作用 應用場景
Class.forName(“全類名”) 通過指定的字元串路徑擷取 多用于配置檔案,将類名定義在配置檔案中。讀取檔案,加載類
類名.class 通過類名的屬性class擷取 多用于參數的傳遞
對象.getClass() 通過對象的getClass()方法擷取 多用于對象的擷取位元組碼的方式
其他的一些方法
  1. String getSimpleName(); 獲得簡單類名,隻是類名,沒有包

    String getName(); 擷取完整類名,包含包名+類名

  2. T newInstance() ;建立此 Class 對象所表示的類的一個新執行個體。要求:類必須有public的無參數構造方法

擷取構造方法對象

在通過反射擷取構造方法之前,請一定保證存在了相應的Class對象。

  1. public Constructor[] getConstructors()

    : 擷取public修飾的所有的構造方法,每個構造方法被封裝成了Constructor類型的對象,被存儲數組中。Constructor類是專門用來描述構造方法的一個類
    Constructor<?>[] cons = c.getConstructors();
    //周遊
    for (Constructor<?> con : cons) {
    	System.out.println(con);
    }
               
  2. public Constructor<T> getConstructor(Class... parameterTypes)

    : 擷取指定參數類型的public修飾的構造方法。如果不存在對應的構造方法,則會抛出

    java.lang.NoSuchMethodException

    異常。

    參數說明:

    Class... parameterTypes

    : 必須傳遞資料類型對應的Class對象,而且是可變參數,可以傳遞數組,參數清單,不傳遞(擷取空參)。

    例如:擷取 構造方法

    public Person(String name, int age) { ...}

    對應的Construtor對象,填寫的參數清單為

    String.class

    int.class

    //擷取public修飾的空參構造方法對象
    Constructor<?> con = c.getConstructor();
    System.out.println(con);
    
    //擷取public修飾的第一個參數是String類型,第二個參數是int類型的構造方法對象
    Constructor<?> con2 = c.getConstructor(String.class, int.class);
    System.out.println(con2);
               

私有方法也能通過其他方法反射出來,但最好不要這麼做這樣的暴力反射,因為破壞了封裝性。如果有這樣的需求,查閱API文檔:

getDeclaredConstructor

等相關方法

使用構造方法對象構造對象

  1. java.lang.reflect.Constructor

    類的成員方法:

    public Object newInstance(Object... params)

    :執行構造方法對象,建立一個具體的對象
    //擷取Class類型的對象
    Class<?> c = Class.forName("domain.Person");
    
    //通過Class類型的對象擷取空參構造方法對象
    Constructor<?> con = c.getConstructor();
    
    //執行構造方法對象,建立一個具體的對象
    //執行的是空參構造方法對象,不需要傳遞參數,傳回的類型是Object,強轉為Person類
    Person p = (Person)con.newInstance();
               
  2. java.lang.Class

    類的成員方法:

    public T newInstance()

    執行空參構造方法,建立一個對象。
    //擷取Class類型的對象
    Class<?> c = Class.forName("domain.Person");
    
    //java.lang.Class類 成員方法: newInstance
    Person p = (Person)c.newInstance();
               

擷取成員方法對象

在擷取成員方法之前,需要擷取Class類型的對象

  1. java.lang.Class

    類的成員方法:

    public Method[] getMethods()

    :擷取所有public修飾的成員方法,包含繼承下來的方法,每個成員方法被封裝成了一個Method對象,存儲Method數組中。

    java.lang.reflect.Method

    類是用來描述成員方法的一個類。
    //擷取Class類型的對象
    Class<?> c = Class.forName("domain.Person");
    
    //擷取所有public修飾的成員方法,包含繼承下來的
    Method[] ms = c.getMethods();
    for (Method m : ms) {
    	System.out.println(m);
    }
               
  2. java.lang.Class

    類的成員方法:

    public Method getMethod(String name, Class... parameterTypes)

    擷取public修飾的指定方法名稱,指定參數類型對應的成員方法對象。參數清單中

    String name

    為方法名稱,

    Class... parameterTypes

    為必須傳遞資料類型對應的Class對象,而且是可變參數,可以傳遞數組,參數清單,不傳遞則擷取空參。
    //擷取Class類型的對象
    Class<?> c = Class.forName("domain.Person");
    
    //擷取public修飾的名稱為toString的沒有參數的方法
    Method m1 = c.getMethod("toString");
    System.out.println(m1);
    
    //擷取public修飾的名稱為setName的參數為String類型的方法
    Method m2 = c.getMethod("setName", String.class);
    System.out.println(m2);
               

使用成員方法對象執行方法

使用

java.lang.reflect.Method

類的成員方法

public Object invoke(Object obj,Object... args)

來對帶有指定參數的指定對象調用由此 Method 對象表示的底層方法。參數

Object obj

為包含成員方法的對象,

Object... arg

執行方法時,該方法需要的具體的參數。傳回值在沒有傳回值的方法傳回一個null,有傳回值的方法則封裝為具體結果。

//1.擷取Class類型的對象
Class<?> c = Class.forName("domain.Person");

//成員方法的調用,需要包含成員方法的對象
//建立一個對象
Object obj = c.newInstance();

//2.擷取setName方法對象
Method setNameMethod = c.getMethod("setName", String.class);
//3.執行setName方法對象
Object result = setNameMethod.invoke(obj, "茶葉蛋");
System.out.println(result);//null
System.out.println(obj);// Person{name='茶葉蛋',age=0}

//2.擷取getName方法對象
Method getNameMethod = c.getMethod("getName");

//3.執行getName方法對象
result = getNameMethod.invoke(obj);
System.out.println(result);
           

類加載器

将class檔案(硬碟)加載到記憶體生成Class對象。

詳情參見

https://chayedan.site/index.php/archives/23/