【Java面試指北】反射(1) 初識反射

大資料王小皮發表於2022-12-18

如果你被問到:什麼是反射?為什麼需要反射、以及反射的應用?你會如何回答呢?
本篇會帶大家初識反射,瞭解反射概念和基本應用。反射的原理以及深入原始碼的探究將會在後面幾篇介紹。

一、什麼是反射?

要理解什麼是反射,我們先看看什麼是「正射」,一個常見的獲取Student的正射如下:

Student student = new Student();

通常 我們都是直接宣告,或者透過 new Student() 直接獲取一個 Student 類,然後再使用。而一個反射的例子如下:

// 這裡的“com.demo.Student”是需要反射的類的全限定名(包名+類名)
Class clz = Class.forName("com.demo.Student")	
Object stu = clz.newInstance();

先獲取例項的Class類,然後再透過其Class類生成一個Student的Instance。以上兩種方式(new Student和clz.newInstance)是效果是等價的,都是獲取到了一個Student 的例項。

那麼什麼是反射呢?反射是Java中的一個重要的特性,使用反射可以在執行時動態生成物件、獲取物件屬性以及呼叫物件方法。
Oracle 官方對反射的解釋是:

Reflection enables Java code to discover information about the fields, methods and constructors of loaded classes, and to use reflected fields, methods, and constructors to operate on their underlying counterparts, within security restrictions.
The API accommodates applications that need access to either the public members of a target object (based on its runtime class) or the members declared by a given class. It also allows programs to suppress default reflective access control.

反射的核心是 JVM 在執行時才動態載入類或呼叫方法/訪問屬性,它不需要事先(寫程式碼的時候或編譯期)知道執行物件是誰。

反射的問題:
這裡先簡單提一下:反射相當於一系列解釋操作,通知 JVM 要做的事情,效能比直接的 Java 程式碼要慢很多。

二、為什麼需要反射?

舉一個直觀的例子(僅為了說明其中一種用法):
如果我讓你寫一個根據執行時輸入的名字進行列印輸出,你會寫出類似下面的程式碼:

public void sayHello(String name) {
    // 在執行前根本不知道 name 是什麼,只有在執行時 name 才會被確認並列印出來
    System.out.println("hello, " + name);
}

那麼同樣的,在寫程式碼時可能也不知道要用什麼類,執行時才知道。比如載入資料庫驅動的時候,你可以直接 new 出來具體的驅動類,但要是換了資料庫呢,還要修改原始碼重新打包更新麼?

new com.mysql.jdbc.Driver();

那你可能會說,我多寫幾個 if else 不就行了,類似下面這樣:

if ( xxx == "mysql") {
    new com.mysql.jdbc.Driver();
else if ( xxx == "redis" ) {
    new com.redis.jdbc.Driver();
else if ( ... ){
}

這樣的問題是,在編譯期就要湊齊所有的 jdbc 連線庫,甭管用不用這些都會被載入到記憶體中,資料庫型別多了會有極大的浪費。
那麼這種情況,就可以用反射來解決,在執行時才去動態的載入對應類。你也可以在配置檔案中指明要使用哪種資料庫類,連線不同的資料庫都可以使用這一份程式。

// 反射的方式動態載入類
Class.forName("com.mysql.jdbc.Driver");

三、反射的基本使用

下面介紹透過反射都能做什麼:

一)獲得 Class 物件

// 1 使用 Class 類的 forName 靜態方法
 Class.forName(driver);

// 2 直接獲取某一個物件的 class
Class<?> cl = int.class;

// 3 呼叫某個物件的 getClass() 方法
StringBuilder str = new StringBuilder("123");
Class<?> klass = str.getClass();

二)判斷是否為某個類的例項

public static void displayObjectClass(Object o) {
    if (o instanceof Vector)
   		System.out.println("物件是 java.util.Vector 類的例項");
  	else if (o instanceof ArrayList)
   		System.out.println("物件是 java.util.ArrayList 類的例項");
   	else
   		System.out.println("物件是 " + o.getClass() + " 類的例項");
}

三)建立例項

Class<?> c = String.class;
Object str = c.newInstance();

四)獲取方法

getDeclaredMethods() 方法返回類或介面宣告的所有方法,包括公共、保護、預設(包)訪問和私有方法,但不包括繼承的方法。
getMethods() 方法返回某個類的所有公用(public)方法,包括其繼承類的公用方法。
getMethod() 方法返回一個特定的方法,其中第一個引數為方法名稱,後面的引數為方法的引數對應Class的物件。

public class ReflectDemo {
	public static void main(String[] args) throws IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
	    Class<?> c = MyClass.class;

	    Method[] methods = c.getMethods();
	    Method[] declaredMethods = c.getDeclaredMethods();
	    Method method = c.getMethod("add", int.class, int.class);

        System.out.println("getMethods獲取的方法:");
        for(Method m:methods)
            System.out.println(m);

        System.out.println("getDeclaredMethods獲取的方法:");
        for(Method m:declaredMethods)
            System.out.println(m);
    }
}

class MyClass {
    public int add(int a, int b) {
        return a + b;
    }
    public int sub(int a, int b) {
        return a - b;
    }
}

// 輸出
/*
getMethods獲取的方法:
public int com.shuofxz.basic.ReflectDemo$MyClass.add(int,int)
public int com.shuofxz.basic.ReflectDemo$MyClass.sub(int,int)
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獲取的方法:
public int com.shuofxz.basic.ReflectDemo$MyClass.add(int,int)
public int com.shuofxz.basic.ReflectDemo$MyClass.sub(int,int)
*/

五)呼叫方法

當我們從類中獲取了一個方法後,我們就可以用 invoke() 來呼叫這個方法。

public class ReflectDemo {
	public static void main(String[] args) throws IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
        Class<?> mc = MyClass.class;
        Object obj = mc.newInstance();
        //獲取methodClass類的add方法
        Method method = mc.getMethod("add", int.class, int.class);
        //呼叫method對應的方法 => add(1,4)
        Object result = method.invoke(obj, 1, 4);
        System.out.println(result);
    }
}

六)獲取構造器、類的成員變數(欄位)資訊

  • 透過 Class 類的 getConstructor 方法得到 Constructor 類的一個例項
  • getFiled:訪問公有的成員變數
  • getDeclaredField:所有已宣告的成員變數,但不能得到其父類的成員變數

四、小結

本篇文章初步介紹了反射機制。讓大家瞭解了反射是什麼,為什麼會有反射這個功能,以及一些基本使用方式。後續文章將會反射的機制和原理做進一步的講解。

相關文章