切換語言為:簡體

巧用反射與註解快速提取類的屬性名為字串列表

  • 爱糖宝
  • 2024-09-28
  • 2049
  • 0
  • 0

最近我在處理一個專案,遇到了一個需求:需要將一個類的屬性轉化為字串列表的形式,比如某個類有屬性 idusernameemail,我要把這些屬性轉換成 ["id", "username", "email"] 這樣的字串陣列。這個需求看似簡單,但如何做到通用性,並應用到不同類的場景呢?

解決方案

Java 的反射機制

要將一個類的屬性名(欄位名)提取出來,並將它們轉換為 ["id", "username", "email"] 這種字串表示,可以透過 反射 來實現。透過反射可以獲取類中的所有欄位,然後將欄位名轉換成字串。

  1. 使用 Java 的 反射機制,透過 Class 物件獲取類的所有欄位。

  2. 遍歷這些欄位,並將欄位名轉換為一個列表。

  3. 將列表轉換成特定格式(例如,["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]
    }
}

程式碼解析

  1. 反射獲取欄位:透過 clazz.getDeclaredFields() 獲取類的所有欄位,這會返回一個 Field[] 陣列,包含類中的所有屬性。

  2. 對映欄位名:使用 Arrays.stream(fields).map(Field::getName) 方法,將每個 Field 物件對映為它的欄位名。

  3. 轉換為列表:使用 Collectors.toList() 將對映後的欄位名收集為一個 List<String>

  4. 生成字串fieldNames.toString() 會自動將 List<String> 轉換為特定的格式 ["id", "username", "email"]

執行輸出結果:

[id, username, email]

進一步最佳化:自定義欄位名和忽略欄位

在實際的應用場景中,這裏不希望直接使用屬性名作為輸出,或者需要忽略某些欄位。比如,我需要把 userName 轉換成 user_name,而忽略掉 password 欄位。爲了解決這個問題,我們可以透過 Java 註解 來控制欄位的轉換方式。

爲了解決這個問題,我設計了兩個自定義註解:

  1. @AppRecordName:用於自定義某個欄位的名稱。

  2. @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();


透過 反射自定義註解,我們可以靈活地將類的屬性名提取出來,並根據需求生成自定義格式的字串。這種方式不僅通用性強,還可以透過註解實現對欄位的自定義控制,適用於複雜的類結構。

0則評論

您的電子郵件等資訊不會被公開,以下所有項目均必填

OK! You can skip this field.