1. 程式人生 > 實用技巧 >反射(Reflection)

反射(Reflection)

Java學習筆記——反射(Reflection)

關於反射

能夠分析類能力的程式稱之為反射(Reflection)

反射機制可以用來:

  1. 在執行時分析類的能力
  2. 在執行時檢查物件,例如:編寫一個適合所有類的toString()
  3. 實現泛型陣列操作程式碼
  4. 利用Method物件(類似C++中的函式指標)
  5. 使用者介面生成器

Class類

在程式執行時,JRE始終為所有物件維護一個執行時型別標識(個人猜測實際上是一個結構體)。這個型別標識會跟蹤記錄每一個物件所屬的類。虛擬機器利用這些資訊來保證呼叫正確的方法

Class類儲存了型別標識Class類是泛型類,你懂我什麼意思的吧)內部的資訊,因此可以使用這個特殊的類來訪問這些資訊。

那麼何如獲取這些資訊呢?

Object類中有一個getClass(),可以返回一個Class型別例項

class cl = e.getclass();

class類中有個方法getName(),可以獲取物件所屬的類名(字串形式),當然如果這個類在一個包中,則返回的類名也會包含包名。

var ram = new Random();
Class cl = ram.getClass();
String name = cl.getName()  // name is "java.util.Random"

當然java還有一種方便的方法來獲取每一個類的Class類物件。如果T是任意的Java型別,則T.class

代表該類的Class類物件

Class cl = Random.class;
Class c2 = int[].class;
Class c3 = int.class;		

Class物件實際上表示的是一種型別(type),這種型別可以是類,亦可以不是類,因此int.class、int[].class是合法的。

如果我想實現動態載入載入類,或者我現在知道這個類的類名(或者介面名),則還可以使用Class類本身的靜態方法來實現類載入

String classname = "java.util.Random";
try{
	Class cl = Class.forName(classname);
}catch(Exception e){
	e.printStackTrace();
}

classname不是一個介面或者類,則會丟擲檢查型異常。因此捕獲異常。

使用這種方法獲得Class物件,classname對應的類會被載入,也就是說類裡面的靜態程式碼塊(Static Code)會被執行。同時可以變換classname的值來實現動態載入類。

更加準確的型別比較

JVM為每一種型別管理一個唯一的Class類物件,也就是說父類和子類被區分為不同的Class型別,因此可以利用==來進行同型別物件比較

father.getClass() == son.getClass();	// 表示式為False,即便father是son的父類

使用Class類物件構造類例項

前文說過,Class類實際上表示的是一種型別,既然如此我能不能用一個Class類來構造一個類例項呢?答案肯定是能的

使用getConstructor()newInstance()

Class cl = Class.forName(classname);
Object obj = Cl.getConstructor().newInstance();

那如果我想要呼叫有引數的構造器來建立物件呢?

先看看getConstructor()newInstance()方法的宣告:

Constructor getConstructor(Class ...paramterTypes)		// 生成一個構造器物件,並描述這個構造器有什麼引數型別
Object newInstance(Object ...params)					// 生成物件例項,params引數為構造器傳進的引數

因此,可以

Class cl = Class.forName(classname);
Object obj = Cl.getConstructor(int.class).newInstance(25);		// 呼叫classname類中帶int型別的構造器,並傳入引數整型25

資源

Class類通常也用在讀入資源上,例如顯示一張圖片等

載入資源的方法

如果資原始檔和類檔案放在同一個包中,則可以

  • 獲取資源類的Class物件
  • 有些方法需要獲取資源位置的URL則需要呼叫getResource()
  • 如果不想獲取URL而是直接將檔案的所有位元組存放在輸入流中的則需要呼叫getResourceAsStream()
Class cl = ResourceTest.class;
URL aboutURL = c1.getResource("about.png");
Image icon = new Image(aboutURL);

InputStream stream = cl.getResourceAsStream("../Date/about.txt");		// 支援相對和絕對路徑,如果沒找到資源則返回null
var about = new String(stream.readAllBytes(), "UTF-8");

反射應用

利用反射分析類

反射機制中常用來做類分析的重要類:FieldMethodConstructor。這些類都在java.lang.reflect包中

接下來對這幾個類用來分析的方法進行簡單介紹:

Class類

String	  getName()						// 返回該型別的類名字
String    getPackageName()				// 返回該類所在的包名
Field[]   getFields()					// 返回物件的所有公共欄位,包括超類的公共欄位
Field[]   getDeclaredFields()			// 返回物件的全部欄位,如果類中沒有欄位,或者物件是基本型別或者陣列,則返回0長度陣列
Class	  getSuperClass()				// 獲取該類的父類Class物件
Method[]  getMethods()					// 返回物件所屬類或者介面的所有公共方法,包括超類的公共方法
Method[]  getDeclaredMethods()			// 返回物件所屬類或者介面的全部方法,不包括超類
Constructor[] getConstructors()			// 返回這個類的所有公共構造器
Constructor[] getDeclaredConstructors()	// 返回全部構造器

Field類

String	getName()		// 返回類中的欄位名(屬性名)的字串
Class	getType()		// 返回欄位的型別(int、long、Date...)
int		getModifiers()	// 獲取欄位的修飾符(public、static、final...),返回1/0的二進位制標誌位,可以配合reflect包中的toString()來顯示具體的修飾符
Class	getDeclaringClass()	//獲取欄位所屬的類對應的Class物件

Method類

String	getName()		// 返回類中的方法名的字串
Class	getReturnType()		// 返回方法的返回值型別對應的Class物件(int、long、Date...)
int		getModifiers()	// 獲取方法的修飾符(public、static、final...),返回1/0的二進位制標誌位,可以配合reflect包中的toString()來顯示具體的修飾符
Class	getDeclaringClass()	//獲取方法所屬的類對應的Class物件
Class[] getParameterTypes()	// 返回Class物件的陣列,其中各個物件表示引數的型別
Class[] getExceptionTypes() // 返回Class物件陣列,其中各個物件表示該方法所丟擲的異常的型別

Constructor類

String	getName()		// 返回類中的構造方法的字串
int		getModifiers()	// 獲取構造方法的修飾符(public、static、final...),返回1/0的二進位制標誌位,可以配合reflect包中的toString()來顯示具體的修飾符
Class	getDeclaringClass()	//獲取構造方法所屬的類對應的Class物件
Class[] getParameterTypes()	// 返回Class物件的陣列,其中各個物件表示引數的型別
Class[] getExceptionTypes() // 返回Class物件陣列,其中各個物件表示該方法所丟擲的異常的型別

Modifier類

static String toString(int modifiers)
static boolean isAbstract(int modifiers)
static boolean isFinal(int modifiers)
static boolean isInterface(int modifiers)
static boolean isNative(int modifiers)
static boolean isPrivate(int modifiers)
static boolean isProtected(int modifiers)
static boolean isPublic(int modifiers)
static boolean isStatic(int modifiers)
static boolean isStrict(int modifiers)
static boolean isSynchronized(int modifiers)
static boolean isVolatile(int modifiers)

下面將演示一個通過反射來分析一個類的demo:

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.security.PrivateKey;

public class Test {
    public static void main(String[] args) {
        new Test("java.lang.Double");
    }
    public Test(String classname){
        try {
            Class c = Class.forName(classname);
            printClass(c);
        }catch (Exception e){
            e.printStackTrace();
        }

    }
    public void printClass(Class c){
        Class sc = c.getSuperclass();						// 獲取父類
        String modifier = Modifier.toString(c.getModifiers());// 獲取類修飾符
        if(modifier.length()>0)
            System.out.print(modifier + " ");     			
        System.out.print("class " + c.getName());			// class + 類名
        if(sc!=null && sc != Object.class)
            System.out.print(" extends " + sc.getName());	// 繼承的父類
        System.out.println();
        System.out.println("{");
        printConstructor(c);								// 獲取構造器函式
        System.out.println();
        printField(c);										// 獲取欄位名
        System.out.println();
        printMethod(c);										// 獲取方法名
        System.out.println("}");
    }
    private void printField(Class c){
        Field[] fields = c.getDeclaredFields();				// 獲取欄位名
        for (Field f:fields
             ) {
            Class type = f.getType();						// 欄位型別
            String name = f.getName();						// 欄位名
            System.out.print("    ");
            String midifier = Modifier.toString(f.getModifiers());
            if(midifier.length()>0)
                System.out.print(midifier + " ");			// 欄位修飾符
            System.out.println(type.getName() + " " + name + ";");
        }
    }
    private void printConstructor(Class c){
        Constructor[] constructors = c.getConstructors();	// 獲取構造方法名稱
        for (Constructor constructor:constructors
        ) {
            String midifier = Modifier.toString(constructor.getModifiers());
            String methodName = constructor.getName();		// 獲取構造方法名
            Class[] Params = constructor.getParameterTypes();// 獲取引數型別
            Class[] exceptions = constructor.getExceptionTypes();	// 獲取異常型別
            System.out.print("    ");
            if(midifier.length()>0)
                System.out.print(midifier + " ");			// 獲取修飾符
            System.out.print(methodName + "(");			 
            for(int i=0; i<Params.length; i++){
                if(i == Params.length - 1)
                    System.out.print( Params[i].getName());
                else
                    System.out.print( Params[i].getName() + ", ");
            }
            System.out.print(")");
            if(exceptions.length>0)
                System.out.print("throws ");				// 獲取異常型別
            for(int i=0; i<exceptions.length; i++){
                if(i == exceptions.length - 1)
                    System.out.print( exceptions[i].getName());
                else
                    System.out.print( exceptions[i].getName() + ", ");
            }
            System.out.println(";");
        }

    }

    private void printMethod(Class c){
        Method[] methods = c.getDeclaredMethods();
        for (Method m:methods
             ) {
            String midifier = Modifier.toString(m.getModifiers());
            String ret = m.getReturnType().getName();
            String methodName = m.getName();
            Class[] Params = m.getParameterTypes();
            Class[] exceptions = m.getExceptionTypes();
            System.out.print("    ");
            if(midifier.length()>0)
                System.out.print(midifier + " ");
            System.out.print(ret + " " + methodName + "(");
            for(int i=0; i<Params.length; i++){
               if(i == Params.length - 1)
                   System.out.print( Params[i].getName());
               else
                   System.out.print( Params[i].getName() + ", ");
            }
            System.out.print(")");
            if(exceptions.length>0)
                System.out.print("throws ");
            for(int i=0; i<exceptions.length; i++){
                if(i == exceptions.length - 1)
                    System.out.print( exceptions[i].getName());
                else
                    System.out.print( exceptions[i].getName() + ", ");
            }
            System.out.println(";");
        }
    }
}

輸出資訊:

public final class java.lang.Double extends java.lang.Number
{
    public java.lang.Double(double);
    public java.lang.Double(java.lang.String)throws java.lang.NumberFormatException;

    public static final double POSITIVE_INFINITY;
    public static final double NEGATIVE_INFINITY;
    public static final double NaN;
    public static final double MAX_VALUE;
    public static final double MIN_NORMAL;
    public static final double MIN_VALUE;
    public static final int MAX_EXPONENT;
    public static final int MIN_EXPONENT;
    public static final int SIZE;
    public static final int BYTES;
    public static final java.lang.Class TYPE;
    private final double value;
    private static final long serialVersionUID;

    public boolean equals(java.lang.Object);
    public static java.lang.String toString(double);
    public java.lang.String toString();
    public int hashCode();
    public static int hashCode(double);
    public static double min(double, double);
    public static double max(double, double);
    public static native long doubleToRawLongBits(double);
    public static long doubleToLongBits(double);
    public static native double longBitsToDouble(long);
    public volatile int compareTo(java.lang.Object);
    public int compareTo(java.lang.Double);
    public byte byteValue();
    public short shortValue();
    public int intValue();
    public long longValue();
    public float floatValue();
    public double doubleValue();
    public static java.lang.Double valueOf(double);
    public static java.lang.Double valueOf(java.lang.String)throws java.lang.NumberFormatException;
    public static java.lang.String toHexString(double);
    public static int compare(double, double);
    public java.lang.Double resolveConstantDesc(java.lang.invoke.MethodHandles$Lookup);
    public volatile java.lang.Object resolveConstantDesc(java.lang.invoke.MethodHandles$Lookup)throws java.lang.ReflectiveOperationException;
    public java.util.Optional describeConstable();
    public static boolean isNaN(double);
    public boolean isNaN();
    public static boolean isInfinite(double);
    public boolean isInfinite();
    public static boolean isFinite(double);
    public static double sum(double, double);
    public static double parseDouble(java.lang.String)throws java.lang.NumberFormatException;
}

利用反射在執行時分析物件

前文講過如何利用反射分析一個類的組成,那麼對於類執行時的例項而言,能不能獲取到物件例項的具體值呢?能

要做到這一點,需要用到Field類中的get()set()(同樣Method類、Constructor類也有這個方法),例如看下面的程式碼:

var harry = new Employee("Harry Hacker", 50000, 10, 1, 1989);
Class cl = harry.getClass();
Field f = cl.getDeclaredField("name");
// the 'name' field of the Employee class
object v = f.get(harry);		// 獲取harry物件中欄位為name的值
// output:“Harry Hacker”

同樣更改值,可以使用:

f.set(harry, "Askia");		// 設定harry物件中欄位為name的值

當然上面的get()set()程式碼存在問題,因為name欄位修飾符是private,因此對該欄位的值進行訪問會丟擲illegalAccessException

Java安全機制允許檢視一個物件有哪些欄位,但是除非擁有訪問許可權,否則不能對這些欄位進行讀寫。

那麼就真的沒有辦法對這些欄位進行強制修改了嗎?也不是,我們可以呼叫setAccessible()來覆蓋java的訪問控制

f.setAccessible(true);
f.set(harry, "Askia");
// now harry.name is "Askia"

通用的toString()

使用反射呼叫任意的方法

使用反射編寫泛型陣列程式碼