切换语言为:繁体

巧用反射与注解快速提取类的属性名为字符串列表

  • 爱糖宝
  • 2024-09-28
  • 2044
  • 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.