最近我在處理一個專案,遇到了一個需求:需要將一個類的屬性轉化為字串列表的形式,比如某個類有屬性
id
、username
、["id", "username", "email"]
這樣的字串陣列。這個需求看似簡單,但如何做到通用性,並應用到不同類的場景呢?
解決方案
Java 的反射機制
要將一個類的屬性名(欄位名)提取出來,並將它們轉換為 ["id", "username", "email"]
這種字串表示,可以透過 反射 來實現。透過反射可以獲取類中的所有欄位,然後將欄位名轉換成字串。
使用 Java 的 反射機制,透過
Class
物件獲取類的所有欄位。遍歷這些欄位,並將欄位名轉換為一個列表。
將列表轉換成特定格式(例如,
["id", "username", "email"]
)。
程式碼實現如下所示:
import java.lang.reflect.Field; import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; public class FieldNameUtil { // 通用方法:將類的欄位名轉換為 ["id", "username", "email"] 這種格式的字串 public static String getFieldNamesAsString(Class<?> clazz) { // 獲取類的所有欄位 Field[] fields = clazz.getDeclaredFields(); // 提取欄位名並轉為List<String> List<String> fieldNames = Arrays.stream(fields) .map(Field::getName) // 獲取欄位名 .collect(Collectors.toList()); // 將List<String> 轉換為類似 ["id", "username", "email"] 的字串 return fieldNames.toString(); } public static void main(String[] args) { // 假設有個User類 class User { private Long id; private String username; private String email; } // 呼叫方法獲取欄位名的字串表示 String result = getFieldNamesAsString(User.class); System.out.println(result); // 輸出: [id, username, email] } }
程式碼解析
反射獲取欄位:透過
clazz.getDeclaredFields()
獲取類的所有欄位,這會返回一個Field[]
陣列,包含類中的所有屬性。對映欄位名:使用
Arrays.stream(fields).map(Field::getName)
方法,將每個Field
物件對映為它的欄位名。轉換為列表:使用
Collectors.toList()
將對映後的欄位名收集為一個List<String>
。生成字串:
fieldNames.toString()
會自動將List<String>
轉換為特定的格式["id", "username", "email"]
。
執行輸出結果:
[id, username, email]
進一步最佳化:自定義欄位名和忽略欄位
在實際的應用場景中,這裏不希望直接使用屬性名作為輸出,或者需要忽略某些欄位。比如,我需要把 userName
轉換成 user_name
,而忽略掉 password
欄位。爲了解決這個問題,我們可以透過 Java 註解 來控制欄位的轉換方式。
爲了解決這個問題,我設計了兩個自定義註解:
@AppRecordName
:用於自定義某個欄位的名稱。@IgnoreAppRecord
:用於忽略某個欄位的轉化。
實現思路:
透過反射獲取欄位時,檢查每個欄位是否存在自定義註解。
如果欄位有
@AppRecordName
註解,使用註解中定義的值作為欄位名。如果欄位有
@IgnoreAppRecord
註解,忽略該欄位。如果欄位沒有註解,按預設的欄位名進行轉化。
定義註解的程式碼如下所示:
import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; // 自定義註解:用於指定欄位的別名 @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface AppRecordName { String value(); } // 自定義註解:用於忽略欄位 @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface IgnoreAppRecord { }
然後實現通用工具類的改造,透過這些註解,我們可以在工具類中增加邏輯,動態檢測註解,從而實現更靈活的欄位名轉化。:
import java.lang.reflect.Field; import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; public class AppRecordUtil { // 通用方法:將類的欄位名轉換為 ["id", "username", "email"] 這種格式的字串 public static String getFieldNamesAsString(Class<?> clazz) { // 獲取類的所有欄位 Field[] fields = clazz.getDeclaredFields(); // 提取欄位名並轉為List<String> List<String> fieldNames = Arrays.stream(fields) .filter(field -> !field.isAnnotationPresent(IgnoreAppRecord.class)) // 忽略被 @IgnoreAppRecord 標記的欄位 .map(field -> { // 如果有 @AppRecordName 註解,則使用註解中的值作為欄位名 if (field.isAnnotationPresent(AppRecordName.class)) { return field.getAnnotation(AppRecordName.class).value(); } // 否則,使用預設欄位名 return field.getName(); }) .collect(Collectors.toList()); // 將 List<String> 轉換為類似 ["id", "username", "email"] 的字串 return fieldNames.toString(); } public static void main(String[] args) { // 假設有個 User 類 class User { private Long id; @AppRecordName("user_name") // 自定義欄位名 private String username; @IgnoreAppRecord // 忽略該欄位 private String password; private String email; // 預設使用屬性名 } // 呼叫方法獲取欄位名的字串表示 String result = AppRecordUtil.getFieldNamesAsString(User.class); System.out.println(result); // 輸出: [id, user_name, email] } }
執行輸出結果:
[id, user_name, email]
擴充套件:類級別忽略欄位
有時我們可能想忽略多個欄位,為此可以使用一個類級別的註解,來批次忽略欄位。如下:
// 自定義註解:用來忽略多個欄位 (類級別) @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface IgnoreAppFields { String[] value(); // 要忽略的欄位集合 }
在工具類中,我們可以透過這個註解來處理類級別的欄位忽略邏輯:
IgnoreAppFields ignoreAppFields = clazz.getAnnotation(IgnoreAppFields.class); List<String> ignoreFields = ignoreAppFields != null ? Arrays.asList(ignoreAppFields.value()) : List.of();
透過 反射 和 自定義註解,我們可以靈活地將類的屬性名提取出來,並根據需求生成自定義格式的字串。這種方式不僅通用性強,還可以透過註解實現對欄位的自定義控制,適用於複雜的類結構。