1. 取得欄位資訊
getFields() 與 getDeclaredFields() 有何不同
在 Java 中,每個類別都有欄位(fields)— 定義在類別內的變數。透過反射,我們可以得知它們的名稱、型別、存取修飾詞(public/private),並在執行期間加以存取。主要入口點是 Class<?> 物件。
Class 類別中用於處理欄位的主要方法:
| 方法 | 回傳內容 |
|---|---|
|
類別、其父類別與介面的所有 public 欄位陣列 |
|
所有 在此類別中宣告的欄位陣列 |
類比:getFields() 就像只參觀展示大廳(public),而 getDeclaredFields() 連技術區(private、protected、package-private)也會帶你看。
範例:列出類別的所有欄位
假設我們有一個類別:
public class Person {
public String name;
private int age;
protected String email;
}
取得欄位資訊:
import java.lang.reflect.Field;
Class<?> clazz = Person.class;
// 所有 public 欄位(包含繼承而來的)
System.out.println("Public fields:");
for (Field field : clazz.getFields()) {
System.out.println(field.getName() + " : " + field.getType().getSimpleName());
}
// 此類別中宣告的所有欄位(包含 private,僅限本類別)
System.out.println("\nDeclared fields:");
for (Field field : clazz.getDeclaredFields()) {
System.out.println(field.getName() + " : " + field.getType().getSimpleName());
}
結果:
Public fields:
name : String
Declared fields:
name : String
age : int
email : String
如何取得欄位的修飾詞?
每個欄位(Field)都有修飾詞(public/private/protected、static、final 等)。可以透過 getModifiers() 取得,並用 Modifier 轉成字串:
import java.lang.reflect.Modifier;
for (Field field : clazz.getDeclaredFields()) {
int mods = field.getModifiers();
System.out.println(field.getName() + " : " + Modifier.toString(mods));
}
2. 取得方法資訊
方法 getMethods() 與 getDeclaredMethods()
方法是物件可以執行的動作。透過反射可以得知類別擁有哪些方法、其參數、回傳型別、修飾詞與註解。
| 方法 | 回傳內容 |
|---|---|
|
類別及其父類別(包含 Object)的所有 public 方法 |
|
在此類別中宣告的所有方法(包含 private) |
範例:列出類別的所有方法
import java.lang.reflect.Method;
System.out.println("Public methods:");
for (Method method : clazz.getMethods()) {
System.out.println(method.getName());
}
System.out.println("\nDeclared methods:");
for (Method method : clazz.getDeclaredMethods()) {
System.out.println(method.getName());
}
結果(對於 Person):
Public methods:
getClass
hashCode
equals
toString
notify
notifyAll
wait
wait
wait
Declared methods:
(沒有,如果 Person 沒有宣告自己的方法)
在 Person 中新增方法:
public class Person {
public String name;
private int age;
protected String email;
public void sayHello() {
System.out.println("Hi!");
}
}
現在 getDeclaredMethods() 也會顯示它。
如何取得方法的參數與回傳型別?
for (Method method : clazz.getDeclaredMethods()) {
System.out.print(Modifier.toString(method.getModifiers()) + " ");
System.out.print(method.getReturnType().getSimpleName() + " ");
System.out.print(method.getName() + "(");
Class<?>[] params = method.getParameterTypes();
for (int i = 0; i < params.length; i++) {
System.out.print(params[i].getSimpleName());
if (i < params.length - 1) System.out.print(", ");
}
System.out.println(");");
}
輸出:
public void sayHello();
3. 取得建構子資訊
建構子是用來建立物件的特殊方法。
| 方法 | 回傳內容 |
|---|---|
|
所有 public 建構子 |
|
類別中宣告的所有建構子 |
範例:列出類別的所有建構子
import java.lang.reflect.Constructor;
System.out.println("Constructors:");
for (Constructor<?> constructor : clazz.getDeclaredConstructors()) {
System.out.print(clazz.getSimpleName() + "(");
Class<?>[] params = constructor.getParameterTypes();
for (int i = 0; i < params.length; i++) {
System.out.print(params[i].getSimpleName());
if (i < params.length - 1) System.out.print(", ");
}
System.out.println(");");
}
如果類別只有預設建構子,也會被列出。
4. 註解:如何得知類別、方法或欄位上標註了什麼
註解是可加在類別、方法、欄位與參數上的特殊標記。透過反射可以得知元素上有哪些註解。
取得註解
- 對於類別:clazz.getAnnotations()
- 對於方法:method.getAnnotations()
- 對於欄位:field.getAnnotations()
範例:檢查是否存在註解 @Deprecated
for (Method method : clazz.getDeclaredMethods()) {
if (method.isAnnotationPresent(Deprecated.class)) {
System.out.println(method.getName() + " is @Deprecated");
}
}
範例:列出類別的所有註解
for (var annotation : clazz.getAnnotations()) {
System.out.println(annotation);
}
5. 實作:用小程式分析類別結構
把上述內容整合,寫一個小工具,根據類別名稱輸出其結構:欄位、方法、建構子與註解。
程式碼範例:ClassInspector
import java.lang.reflect.*;
import java.util.Scanner;
public class ClassInspector {
public static void main(String[] args) throws Exception {
Scanner scanner = new Scanner(System.in);
System.out.print("請輸入類別的完整名稱(例如,java.util.ArrayList):");
String className = scanner.nextLine();
Class<?> clazz = Class.forName(className);
System.out.println("\n== 類別: " + clazz.getName() + " ==");
// 類別的註解
System.out.println("註解:");
for (Annotation annotation : clazz.getAnnotations()) {
System.out.println(" " + annotation);
}
// 欄位
System.out.println("\n欄位:");
for (Field field : clazz.getDeclaredFields()) {
System.out.println(" " + Modifier.toString(field.getModifiers())
+ " " + field.getType().getSimpleName()
+ " " + field.getName());
}
// 方法
System.out.println("\n方法:");
for (Method method : clazz.getDeclaredMethods()) {
System.out.print(" " + Modifier.toString(method.getModifiers())
+ " " + method.getReturnType().getSimpleName()
+ " " + method.getName() + "(");
Class<?>[] params = method.getParameterTypes();
for (int i = 0; i < params.length; i++) {
System.out.print(params[i].getSimpleName());
if (i < params.length - 1) System.out.print(", ");
}
System.out.println(");");
}
// 建構子
System.out.println("\n建構子:");
for (Constructor<?> constructor : clazz.getDeclaredConstructors()) {
System.out.print(" " + Modifier.toString(constructor.getModifiers())
+ " " + clazz.getSimpleName() + "(");
Class<?>[] params = constructor.getParameterTypes();
for (int i = 0; i < params.length; i++) {
System.out.print(params[i].getSimpleName());
if (i < params.length - 1) System.out.print(", ");
}
System.out.println(");");
}
}
}
這怎麼運作?
- 使用者輸入類別的完整名稱(例如 "java.util.ArrayList" 或自己的類別)。
- 程式動態載入該類別,並輸出其註解、欄位、方法與建構子。
- 試試看 JDK 的標準類別 — 你會發現裡面藏了很多有趣的東西!
6. 實用細節與視覺化
視覺化示意:透過反射可以取得哪些類別資訊
graph TD
A[Class<?>] --> B["getFields/getDeclaredFields"]
A --> C[getMethods/getDeclaredMethods]
A --> D[getConstructors/getDeclaredConstructors]
A --> E[getAnnotations]
B --> F[Field: 類型、名稱、修飾詞]
C --> G[Method: 回傳型別、參數]
D --> H[Constructor: 參數]
E --> I["Annotation[]"]
表格:在哪裡查詢資訊
| 想知道什麼 | 透過反射如何取得 |
|---|---|
| 所有 public 欄位 | |
| 所有宣告的欄位 | |
| 所有 public 方法 | |
| 所有宣告的方法 | |
| 所有 public 建構子 | |
| 所有宣告的建構子 | |
| 類別的註解 | |
| 方法/欄位的註解 | |
| 修飾詞 | |
7. 使用反射時的常見錯誤
錯誤 1:混淆 getFields() 與 getDeclaredFields()。
如果你要找的是 private 欄位,請使用 getDeclaredFields(),而不是 getFields()。前者會回傳此類別中宣告的所有欄位,後者只回傳 public 欄位(而且包含繼承而來的!)。
錯誤 2:沒有處理受檢例外(checked exceptions)。
許多反射 API 方法會拋出例外(例如 ClassNotFoundException 或 SecurityException)。別忘了處理它們,或在方法簽章中宣告。
錯誤 3:忽略了存取修飾詞。
要存取 private 欄位與方法,必須先呼叫 setAccessible(true),否則會得到 IllegalAccessException。範例:
Field field = clazz.getDeclaredField("age");
field.setAccessible(true); // 開放存取 private 欄位
int value = (int) field.get(person);
錯誤 4:只期望看到自己宣告的方法/欄位。
getMethods() 與 getFields() 會回傳不只目前類別的 public 成員,還包含所有父類別(包含 Object)的成員。如果你只想看到自己寫的東西,這點可能會讓你驚訝。
錯誤 5:沒有正確檢查註解是否存在。
請使用 isAnnotationPresent() 來檢查特定註解,而不是沒必要地手動遍歷註解陣列。
GO TO FULL VERSION