CodeGym /課程 /JAVA 25 SELF /反射簡介:Class 類,為何需要

反射簡介:Class 類,為何需要

JAVA 25 SELF
等級 62 , 課堂 0
開放

1. 什麼是反射

Java 的反射是一種機制,允許程式在執行期間檢查甚至改變自身的結構。就好比你能夠往自己內部看看並問:「我有哪些欄位與方法?我的建構子是什麼?我到底是什麼類別?」——甚至還能呼叫自己的私有方法。

形式化定義:
反射是一組 API,允許在程式執行時取得類、介面、欄位、方法與建構子的資訊,並對它們進行操作。

為什麼需要反射?

  • 框架:Spring、Hibernate、JUnit 使用反射達成「魔法」:自動建立物件、依賴注入、依名稱或依註解呼叫方法等。
  • 序列化:將物件轉成 JSON/XML 再轉回——需要得知欄位並能存取它們。
  • 測試:自動尋找並呼叫帶有註解的方法(例如 @Test)。
  • 動態載入類別:在執行期依名稱載入類別(外掛、驅動程式)。
  • IDE 與程式碼分析:自動完成、檢查、重構。

實例

public class Person {
    private String name;
    private int age;

    public void sayHello() {
        System.out.println("你好,我叫 " + name);
    }
}

借助反射,我們可以在執行期得知類別 Person 具備欄位 nameage,以及方法 sayHello。更進一步,還能修改私有欄位的值,或依名稱呼叫方法。

一點幽默
可以說,反射就像在《駭客任務》中:你意識到你的程式碼都是可被即時檢查與改變的資料。只是別忘了那顆關於安全性的紅色藥丸!

2. 類別 Class:反射的核心

在 Java 中,每個物件與每個型別在執行期都對應到一個特殊的 Class 物件。這個物件就是該型別的「中繼資訊」。類別 java.lang.Class<T> 是進入反射世界的核心入口。

如何取得 Class 物件?

有幾種方式:

  • 透過 .class
    若在編譯期已知類型,這是最直接且安全的方式:
    Class<Person> personClass = Person.class;
  • 透過物件:getClass()
    如果你已有實例:
    Person p = new Person();
    Class<?> clazz = p.getClass();
    此處的 clazz 是描述物件 p 實際類別的 Class 物件。
  • 透過字串:Class.forName()
    當類名只在執行期才知道時:
    Class<?> clazz = Class.forName("com.example.Person");
    適合載入外掛、驅動程式與其他動態情境。

範例:取得類名

Person p = new Person();
Class<?> clazz = p.getClass();
System.out.println(clazz.getName()); // 輸出:Person 或 com.example.Person

表格:獲取 Class 物件的主要方式

如何取得 何時使用 範例
MyClass.class
類型在編譯期已知時
String.class
object.getClass()
已有物件時
someList.getClass()
Class.forName(name)
類名在執行期才已知時
Class.forName("java.util.Date")

示意圖:物件與其 Class

+-------------------+
|   Person p        |
+-------------------+
         |
         v
+-------------------+
|  p.getClass()     |
+-------------------+
         |
         v
+-------------------+
|   Class<Person>   |
+-------------------+

透過 Class 檢查型別

有時需要判斷某物件是否屬於特定類別:

if (p.getClass() == Person.class) {
    System.out.println("這就是 Person!");
}

若考慮繼承——使用一般的 instanceof 運算子:

if (p instanceof Person) {
    // 如常運作
}

3. 什麼時候需要反射,什麼時候不需要

能力越大,責任越大。反射是強大的工具,但只應在確有必要時使用。

必須使用反射的情境

  • 框架與函式庫:Spring、Hibernate、JUnit、Jackson——自動化、「魔法」,減少「手寫」程式碼。
  • 序列化:需要了解物件欄位,才能轉成 JSON/XML。
  • 外掛與擴充:類別動態載入,其結構事先未知。

為何不要濫用反射

  • 失去型別安全:錯誤會在執行期才浮現。
  • 維護困難:程式碼變得不那麼直觀。
  • 效能:比直接呼叫更慢。
  • 安全性:可能繞過封裝,暴露私有資料。

範例:不需要反射的情況

p.sayHello();

範例:反射不可或缺的時候
你在撰寫一個迷你測試框架:使用者以 @Test 標註方法,程式需要自動找到並呼叫它們——沒有反射就做不到。

4. 示範:認識 Class

範例:輸出類名與其超類

public class ReflectionDemo {
    public static void main(String[] args) {
        String s = "Hello, reflection!";
        Class<?> clazz = s.getClass();

        System.out.println("類名: " + clazz.getName());
        System.out.println("簡單類名: " + clazz.getSimpleName());
        System.out.println("套件: " + clazz.getPackageName());
        System.out.println("超類: " + clazz.getSuperclass().getName());
    }
}

結果:

類名: java.lang.String
簡單類名: String
套件: java.lang
超類: java.lang.Object

範例:取得基本型別的 Class

Class<Integer> intClass = int.class;
System.out.println(intClass.getName()); // int

Class<?> doubleClass = double.class;
System.out.println(doubleClass.getName()); // double

範例:取得陣列的 Class

int[] arr = new int[10];
Class<?> arrClass = arr.getClass();
System.out.println(arrClass.getName()); // [I(陣列的特殊格式)

範例:檢查是否為陣列型別

if (arrClass.isArray()) {
    System.out.println("這是陣列!");
}

5. 實作:小程式「這是什麼類?」

我們來寫個程式,讀入完整類名(例如 "java.util.ArrayList"),並輸出其基本資訊。

import java.util.Scanner;

public class ClassInfoPrinter {
    public static void main(String[] args) throws Exception {
        Scanner scanner = new Scanner(System.in);
        System.out.print("請輸入完整類名: ");
        String className = scanner.nextLine();

        Class<?> clazz = Class.forName(className);

        System.out.println("類名: " + clazz.getName());
        System.out.println("套件: " + clazz.getPackageName());
        System.out.println("超類: " + clazz.getSuperclass().getName());

        Class<?>[] interfaces = clazz.getInterfaces();
        System.out.print("實作的介面: ");
        for (Class<?> i : interfaces) {
            System.out.print(i.getName() + " ");
        }
        System.out.println();
    }
}

執行範例:

請輸入完整類名: java.util.ArrayList
類名: java.util.ArrayList
套件: java.util
超類: java.util.AbstractList
實作的介面: java.util.List java.util.RandomAccess java.lang.Cloneable java.io.Serializable 

關於 Class 與反射的有趣事實

  • 在 JVM 中,每個已載入的型別都有一個共同代表——Class 物件。同一型別的所有實例共享同一個 Class<T>
  • 可以判斷型別的種類:isInterface()isEnum()isArray()isPrimitive() 等。
  • 若在 Class.forName 中指定錯誤的名稱,會拋出 ClassNotFoundException
  • 透過反射可以建立物件,即使在編譯期不知道其類別——我們會在下一講討論這一點。

6. 初學反射時的常見錯誤

錯誤 1:以為反射很快。
其實反射比一般的方法呼叫與欄位存取更慢。不要在每件小事上都使用它。

錯誤 2:嘗試為不存在的類別取得 Class。
如果類名寫錯,會得到 ClassNotFoundException。請妥善處理此例外。

錯誤 3:把類別物件和實例搞混。
Class 物件描述的是型別的結構,本身不是該型別的實例。

錯誤 4:不必要地使用反射。
能用一般程式碼解決就別用反射。反射適合用在動態場景、框架、外掛等任務。

錯誤 5:不小心操作私有欄位與方法。
反射能繞過封裝,這可能導致錯誤與安全問題,尤其是在大型專案中。

留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION