java 统计对象中的非空字段

6yt4nkrj  于 2022-12-21  发布在  Java
关注(0)|答案(2)|浏览(296)

我有一个UserProfile类,其中包含用户数据,如下所示:

class UserProfile {

  private String userId;
  private String displayName;
  private String loginId;
  private String role;
  private String orgId;
  private String email;
  private String contactNumber;
  private Integer age;
  private String address;

// few more fields ...

// getter and setter
}

我需要计算非null字段,以显示配置文件中用户填充的百分比。此外,还有一些字段我不想在百分比计算中考虑,如:x一米二氮一x、x一米三氮一x和x一米四氮一x。
一个简单的方法是使用多个If语句来获取非空字段count,但这会涉及到大量的代码,而且还有另一个类Organization,我也需要显示它的完成百分比,所以我创建了一个实用函数,如下所示:

public static <T, U> int getNotNullFieldCount(T t,
        List<Function<? super T, ? extends U>> functionList) {
    int count = 0;

    for (Function<? super T, ? extends U> function : functionList) {
        count += Optional.of(t).map(obj -> function.apply(t) != null ? 1 : 0).get();
    }

    return count;
}

然后调用这个函数,如下所示:

List<Function<? super UserProfile, ? extends Object>> functionList = new ArrayList<>();
functionList.add(UserProfile::getAge);
functionList.add(UserProfile::getAddress);
functionList.add(UserProfile::getEmail);
functionList.add(UserProfile::getContactNumber);
System.out.println(getNotNullFieldCount(userProfile, functionList));

我的问题是,这是不是我可以计算非null字段的最好方法,或者我可以进一步改进它。请提出建议。

lyr7nygr

lyr7nygr1#

你可以通过在给定的函数列表上创建一个流来简化你的代码:

public static <T> long getNonNullFieldCount(T t, List<Function<? super T, ?>> functionList) {
    return functionList.stream().map(f -> f.apply(t)).filter(Objects::nonNull).count();
}

这将返回每个函数返回的非null字段的计数。每个函数被Map到将其应用于给定对象的结果,并且null字段被 predicate Objects::nonNull过滤掉。

bzzcjhmw

bzzcjhmw2#

我写了一些实用方法来获取对象中可读属性的总数和非空值的计数。完成百分比可以基于这些来计算。
它应该可以很好地处理继承属性和嵌套对象。
不过,我没有添加忽略某些属性的逻辑。
下面是实用程序类:

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.SneakyThrows;
import org.springframework.lang.NonNull;

import java.beans.IntrospectionException;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class PropertyCountUtils {

    public static PropertyValueCount getReadablePropertyValueCount(Object object) {
        return getReadablePropertyValueCount(object, true);
    }

    @SneakyThrows
    private static PropertyValueCount getReadablePropertyValueCount(Object object, boolean recursively) {
        if (object == null) {
            return null;
        }

        int totalReadablePropertyCount = 0;
        int nonNullValueCount = 0;

        List<Field> fields = getAllDeclaredFields(object);

        for (Field field : fields) {
            PropertyDescriptor propertyDescriptor;
            try {
                propertyDescriptor = new PropertyDescriptor(field.getName(), object.getClass());
            } catch (IntrospectionException e) {
                // ignore field if it doesn't have a getter
                continue;
            }

            Method readMethod = propertyDescriptor.getReadMethod();
            if (readMethod == null) {
                // ignore field if not readable
                continue;
            }

            totalReadablePropertyCount++;

            Object value = readMethod.invoke(object);

            if (value == null) {
                int readablePropertyValueCount = getReadablePropertyCount(readMethod.getReturnType());
                totalReadablePropertyCount += readablePropertyValueCount;
            } else {
                nonNullValueCount++;

                // process properties of nested object
                if (recursively) {
                    boolean circularTypeReference = hasCircularTypeReference(object.getClass(), value.getClass());

                    PropertyValueCount propertyValueCount = getReadablePropertyValueCount(
                            value,
                            // avoid infinite loop
                            !circularTypeReference);

                    totalReadablePropertyCount += propertyValueCount.getTotalCount();
                    nonNullValueCount += propertyValueCount.getNonNullValueCount();
                }
            }
        }

        return PropertyValueCount.builder()
                .totalCount(totalReadablePropertyCount)
                .nonNullValueCount(nonNullValueCount)
                .build();
    }

    @SneakyThrows
    private static int getReadablePropertyCount(@NonNull Class<?> inspectedClass) {
        int totalReadablePropertyCount = 0;

        Field[] fields = inspectedClass.getDeclaredFields();

        for (Field field : fields) {
            PropertyDescriptor propertyDescriptor;
            try {
                propertyDescriptor = new PropertyDescriptor(field.getName(), inspectedClass);
            } catch (IntrospectionException e) {
                // ignore field if it doesn't have a getter
                continue;
            }

            Method readMethod = propertyDescriptor.getReadMethod();
            if (readMethod != null) {
                totalReadablePropertyCount++;

                Class<?> returnType = readMethod.getReturnType();

                // process properties of nested class, avoiding infinite loops
                if (!hasCircularTypeReference(inspectedClass, returnType)) {
                    int readablePropertyValueCount = getReadablePropertyCount(returnType);

                    totalReadablePropertyCount += readablePropertyValueCount;
                }
            }
        }

        return totalReadablePropertyCount;
    }

    private static List<Field> getAllDeclaredFields(Object object) {
        List<Field> fields = new ArrayList<>();
        Collections.addAll(fields, object.getClass().getDeclaredFields());

        Class<?> superClass = object.getClass().getSuperclass();
        while (superClass != null) {
            Collections.addAll(fields, superClass.getDeclaredFields());

            superClass = superClass.getSuperclass();
        }
        return fields;
    }

    private static boolean hasCircularTypeReference(Class<?> propertyContainerClass, Class<?> propertyType) {
        return propertyContainerClass.isAssignableFrom(propertyType);
    }

    @Data
    @AllArgsConstructor
    @Builder
    public static class PropertyValueCount {
        private int nonNullValueCount;
        private int totalCount;
    }
}

下面是一些测试:

import lombok.*;
import lombok.experimental.SuperBuilder;
import org.junit.jupiter.api.Test;

import static org.assertj.core.api.Assertions.assertThat;

public class PropertyCountUtilsTest {

    @Test
    public void getReadablePropertyValueCount_whenAllFieldsPopulated() {
        ChildClass object = ChildClass.builder()
                .propertyA("A")
                .propertyB(TestEnum.VALUE_1)
                .propertyC(1)
                .propertyD(NestedClass.builder()
                        .propertyX("X")
                        .propertyY(TestEnum.VALUE_2)
                        .propertyZ(2)
                        .nestedProperty(NestedClass.builder()
                                .propertyX("X'")
                                .propertyY(TestEnum.VALUE_3)
                                .propertyZ(3)
                                // `nestedProperty` and its properties should still be added to the total count
                                .build())
                        .build())
                .property1("test")
                .property2(TestEnum.VALUE_4)
                .property3(4)
                .property4(NestedClass.builder()
                        .propertyX("test X")
                        .propertyY(TestEnum.VALUE_5)
                        .propertyZ(2)
                        .nestedProperty(NestedClass.builder()
                                .propertyX("test X'")
                                .propertyY(TestEnum.VALUE_6)
                                .propertyZ(3)
                                // `nestedProperty` and its properties should still be added to the total count
                                .build())
                        .build())
                .build();

        PropertyCountUtils.PropertyValueCount propertyValueCount =
                PropertyCountUtils.getReadablePropertyValueCount(object);

        assertThat(propertyValueCount).isNotNull();
        assertThat(propertyValueCount.getTotalCount()).isEqualTo(32);
        assertThat(propertyValueCount.getNonNullValueCount()).isEqualTo(22);
    }

    @Test
    public void getReadablePropertyValueCount_whenSomeFieldsPopulated() {
        ChildClass object = ChildClass.builder()
                .propertyC(1)
                .property3(2)
                .build();

        PropertyCountUtils.PropertyValueCount propertyValueCount =
                PropertyCountUtils.getReadablePropertyValueCount(object);

        assertThat(propertyValueCount).isNotNull();
        // recursive nested properties only counted once
        assertThat(propertyValueCount.getTotalCount()).isEqualTo(16);
        assertThat(propertyValueCount.getNonNullValueCount()).isEqualTo(2);
    }

    @Data
    @SuperBuilder
    private static class SuperClass {

        private String property1;
        private TestEnum property2;
        private int property3;
        private NestedClass property4;

    }

    @Data
    @EqualsAndHashCode(callSuper = true)
    @SuperBuilder
    private static class ChildClass extends SuperClass {

        private String propertyA;
        private TestEnum propertyB;
        private int propertyC;
        private NestedClass propertyD;

    }

    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    @Builder
    private static class NestedClass {

        private String propertyX;
        private TestEnum propertyY;
        private int propertyZ;
        private NestedClass nestedProperty;

    }

    private enum TestEnum {

        VALUE_1,
        VALUE_2,
        VALUE_3,
        VALUE_4,
        VALUE_5,
        VALUE_6

    }

}

相关问题