Java 反射

不該相遇在秋天發表於2018-10-20

一、Java 語言的反射機制

在Java執行時環境中,對於任意一個類,能否知道這個類有哪些屬性和方法?對於任意一個物件,能否呼叫它的任意一個方法?
答案是肯定的。這種動態獲取類的資訊以及動態呼叫物件的方法的功能來自於Java 語言的反射(Reflection)機制。
Java 反射機制主要提供了以下功能:

  1. 在執行時判斷任意一個物件所屬的類。
  2. 在執行時構造任意一個類的物件。
  3. 在執行時判斷任意一個類所具有的成員變數和方法。
  4. 在執行時呼叫任意一個物件的方法

Reflection 允許程式在執行時透過Reflection APIs取得任何一個 已知名稱的class的內部資訊,包括其modifiers(諸如public, static 等等)、superclass(例如Object)、實現之interfaces(例如Serializable),也包括fields和methods的所有資訊,並可於執行時改變fields內容或呼叫methods。

那麼反射這麼厲害,java是不是動態語言呢?
來看一下動態語言百科上的定義:
動態語言,是指程式在執行時可以改變其結構:新的函式可以被引進,已有的函式可以被刪除等在結構上的變化。
從這個角度看很明顯,發現java不是動態語言

儘管在這樣的定義與分類下Java不是動態語言,它卻有著一個非常突出的動態相關機制:Reflection。這個字的意思是“反射、映象、倒影”,用在Java身上指的是我們可以於執行時載入、探知、使用編譯期間完全未知的classes。換句話說,Java程式可以載入一個執行時才得知名稱的class,獲悉其完整構造(但不包括methods定義),並生成其物件實體、或對其fields設值、或呼叫其methods。

二、Java Reflection API 簡介

在JDK中,主要由以下類來實現Java反射機制,這些類都位於java.lang.reflect包中

  1. Class類:代表一個類。
  2. Field 類:代表類的成員變數(成員變數也稱為類的屬性)。
  3. Method類:代表類的方法。
  4. Constructor 類:代表類的構造方法。
  5. Array類:提供了動態建立陣列,以及訪問陣列的元素的靜態方法
    具體如下:
    這裡寫圖片描述
    常用方法用法:

1、獲得物件的型別

Class classType=object.getClass();

在java.lang.Object 類中定義了getClass()方法,因此對於任意一個Java物件,都可以通過此方法獲得物件的型別。Class類是Reflection API 中的核心類,它有以下方法:

  • getName():獲得類的完整名字。
  • getFields():獲得類的public型別的屬性。
  • getDeclaredFields():獲得類的所有屬性。
  • getMethods():獲得類的public型別的方法。
  • getDeclaredMethods():獲得類的所有方法。

getMethod(String name, Class[] parameterTypes):獲得類的特定方法,name引數指定方法的名字,parameterTypes 引數指定方法的引數型別。

getConstructors():獲得類的public型別的構造方法。

getConstructor(Class[] parameterTypes):
獲得類的特定構造方法,parameterTypes 引數指定構造方法的引數型別。

newInstance():通過類的不帶引數的構造方法建立這個類的一個物件。

2、通過預設構造方法建立一個新物件

Object objectCopy=classType.getConstructor(new Class[]{}).newInstance(new Object[]{});

以上程式碼先呼叫Class類的getConstructor()方法獲得一個Constructor 物件,它代表預設的構造方法,然後呼叫Constructor物件的newInstance()方法構造一個例項。

3、獲得物件的所有屬性

Field fields[]=classType.getDeclaredFields();

Class 類的getDeclaredFields()方法返回類的所有屬性,包括public、protected、預設和private訪問級別的屬性。

獲得每個屬性相應的getXXX()和setXXX()方法,然後執行這些方法,把原來物件的屬性拷貝到新的物件中

4、動態建立和訪問陣列

java.lang.Array 類提供了動態建立和訪問陣列元素的各種靜態方法。

三、Class

眾所周知Java有個Object class,是所有Java classes的繼承根源,其內宣告瞭數個應該在所有Java class中被改寫的methods:hashCode()、equals()、clone()、toString()、getClass()等。其中getClass()返回一個Class object。

Class class十分特殊。它和一般classes一樣繼承自Object,其實體用以表達Java程式執行時的classes和interfaces,也用來表達enum、array、primitive Java types
(boolean, byte, char, short, int, long, float, double)以及關鍵詞void。當一個class被載入,或當載入器(class loader)的defineClass()被JVM呼叫,JVM 便自動產生一個Class object。
如果您想借由“修改Java標準庫原始碼”來觀察Class object的實際生成時機(例如在Class的constructor內新增一個println()),不能夠!因為Class並沒有public constructor。

Class是Reflection起源。針對任何您想探勘的class,唯有先為它產生一個Class object,接下來才能經由後者喚起眾多的Reflection APIs。

1、“Class” object的取得途徑。

Java允許我們從多種途徑為一個class生成對應的Class object
這裡寫圖片描述
這裡寫圖片描述
這裡寫圖片描述

四、應用

程式碼操作的實體類:
1、建構函式無參

/**
 * Created by jiankunking on 2016/9/16.
 */
public class entityClassWithoutParameter
{
    public int getAddResult(int a, int b)
    {
        return a + b;
    }

    private int getMultiplyResult(int a, int b)
    {
        return a * b;
    }

    public String getField1()
    {
        return field1;
    }

    public void setField1(String field1)
    {
        this.field1 = field1;
    }

    public String getField2()
    {
        return field2;
    }

    public void setField2(String field2)
    {
        this.field2 = field2;
    }

    public String field1;
    protected String field2;
    private String field3 = "我是私有的";

    /**
     * 測試私有函式是否可見
     * @return
     */
    private String testPrivate(String string)
    {
        System.out.println("testPrivate:"+string);
        return  string;
    }
}


2、建構函式有參

/**
 * Created by jiankunking on 2016/9/16.
 */
public class entityClassWithParameter
{
    public entityClassWithParameter(String string, int num)
    {
        System.out.println("我是entityClassWithParameter:" + string + num);
    }

}

1、執行時生成instances

在Reflection 動態機制中有兩種作法:一個針對“無參ctor”,一個針對“帶引數ctor”。如果欲呼叫的是“帶引數ctor“就比較麻煩些,不再呼叫Class的newInstance(),而是呼叫Constructor 的newInstance()。
無參:

//獲取 指定類
Class classType = Class.forName("entityClassWithoutParameter");
//建立 無參 物件
Object objectCopy = classType.getConstructor(new Class[]{}).newInstance(new Object[]{});

有參:

 //建立 有參 物件
Class cls = Class.forName("entityClassWithParameter");
Class[] paramTypes = {String.class, int.class};
// 方法傳入的引數
Object[] params = {"我是string引數", 23};
Constructor con = cls.getConstructor(paramTypes);
//主要就是這句了
entityClassWithParameter base = (entityClassWithParameter) con.newInstance(params);

2、執行時呼叫methods

首先準備一個Class[]做為引數型別(本例指定其中一個是String,另一個是int),然後以此為自變數呼叫getMethod(),獲得特定的Method object。接下來準備一個Object[]放置引數變數,然後呼叫上述所得之特定Method object的invoke()。

//獲取 指定類
Class classType = Class.forName("entityClassWithoutParameter");
//獲取 指定方法
Method addMethod = classType.getMethod("getAddResult", new Class[]{int.class, int.class});
//呼叫函式 並接收結果
Object result = addMethod.invoke(classType.newInstance(), new Object[]{new Integer(100), new Integer(200)});

為什麼獲得Method object時不需指定回返型別?

因為method overloading機制要求signature必須唯一,而回返型別並非signature的一個成份。換句話說,只要指定了method名稱和引數列,就一定指出了一個獨一無二的method。

3、執行時變更fields內容

首先呼叫Class的getField()並指定field名稱。
獲得特定的Field object之後便可直接呼叫Field的get()和set(),

  //獲取private欄位
        Field f = classType.getDeclaredField("field3");
        //修改protected、private欄位的可見性 public欄位不需要這句
        f.setAccessible(true);
        //建立 無參 物件
        Object objectCopy = classType.getConstructor(new Class[]{}).newInstance(new Object[]{});
        System.out.println("獲取私有欄位值:" + f.get(objectCopy));
        f.set(objectCopy,"我要修改自己");
        System.out.println("獲取私有欄位值:" + f.get(objectCopy));

效果如下:
這裡寫圖片描述

4、對類的私有變數和私有方法的訪問

       //獲取private欄位
        Field f = classType.getDeclaredField("field3");
        //修改protected、private欄位的可見性 public欄位不需要這句
        f.setAccessible(true);
        //建立 無參 物件
        Object objectCopy = classType.getConstructor(new Class[]{}).newInstance(new Object[]{});
        System.out.println("獲取私有欄位值:" + f.get(objectCopy));
        f.set(objectCopy, "我要修改自己");
        System.out.println("獲取私有欄位值:" + f.get(objectCopy));

        //獲得私有方法
        Method method = classType.getDeclaredMethod("testPrivate", new Class[]{String.class});
        //設定私有方法可以被訪問
        method.setAccessible(true);
        //呼叫私有方法
        String str = (String) method.invoke(objectCopy, new Object[]{"測試私有方法是否可以通過反射呼叫"});
        System.out.println("我是私有方法返回值:"+str);//輸出 測試私有方法是否可以通過反射呼叫

完整測試程式碼

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * Created by jiankunking on 2016/7/31.
 */
public class test
{
    public static void main(final String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException, NoSuchFieldException
    {
        //獲取 指定類
        Class classType = Class.forName("entityClassWithoutParameter");
        //獲取 指定方法
        Method addMethod = classType.getMethod("getAddResult", new Class[]{int.class, int.class});
        //呼叫函式 並接收結果
        Object result = addMethod.invoke(classType.newInstance(), new Object[]{new Integer(100), new Integer(200)});
        //輸出 結果
        System.out.println("加法結果:" + (Integer) result);

        //獲取 所有方法
        for (Method item : classType.getDeclaredMethods())
        {
            System.out.println("函式名:" + item.getName());
        }
        //獲取  所有public欄位
        for (Field item : classType.getFields())
        {
            System.out.println("欄位名:" + item.getName());
        }
        //獲取private欄位
        Field f = classType.getDeclaredField("field3");
        //修改protected、private欄位的可見性 public欄位不需要這句
        f.setAccessible(true);
        //建立 無參 物件
        Object objectCopy = classType.getConstructor(new Class[]{}).newInstance(new Object[]{});
        System.out.println("獲取私有欄位值:" + f.get(objectCopy));
        f.set(objectCopy, "我要修改自己");
        System.out.println("獲取私有欄位值:" + f.get(objectCopy));

        //獲得私有方法
        Method method = classType.getDeclaredMethod("testPrivate", new Class[]{String.class});
        //設定私有方法可以被訪問
        method.setAccessible(true);
        //呼叫私有方法
        String str = (String) method.invoke(objectCopy, new Object[]{"測試私有方法是否可以通過反射呼叫"});
        System.out.println("我是私有方法返回值:"+str);//輸出 測試私有方法是否可以通過反射呼叫

        //建立 有參 物件
        Class cls = Class.forName("entityClassWithParameter");
        Class[] paramTypes = {String.class, int.class};
        // 方法傳入的引數
        Object[] params = {"我是string引數", 23};
        Constructor con = cls.getConstructor(paramTypes);
        //主要就是這句了
        entityClassWithParameter base = (entityClassWithParameter) con.newInstance(params);

    }
}

結果如下:
這裡寫圖片描述

本文部分參考百度百科及網路資料:JAVA反射機制

演示demo下載:
http://download.csdn.net/detail/xunzaosiyecao/9631373

作者:jiankunking 出處:http://blog.csdn.net/jiankunking

相關文章