繁体   English   中英

计算对象中的非空字段

[英]Count non null fields in an object

我有一个包含用户数据的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字段以显示用户填写了多少百分比的配置文件。 还有一些我不想在百分比计算中考虑的字段,例如: userIdloginIddisplayName

简单的方法是使用多个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字段的最好方法,还是我可以进一步改进它。 请建议。

您可以通过在给定的函数列表上创建Stream来简单地编写代码:

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字段的计数。 每个函数都映射到将其应用于给定对象的结果,并使用谓词Objects::nonNull过滤掉null字段。

我编写了一些实用方法来获取对象中可读属性的总数和非空值的数量。 可以根据这些计算完成百分比。

它应该可以很好地处理继承属性和嵌套对象。 它可能还需要一些调整来检查集合属性的内容。

这是实用程序类:

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.SneakyThrows;

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;
import java.util.Set;

public class PropertyCountUtils {

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

    public static PropertyValueCount getReadablePropertyValueCount(
            Object object, Set<String> ignoredProperties) {
        return getReadablePropertyValueCount(object, true, ignoredProperties, null);
    }

    @SneakyThrows
    private static PropertyValueCount getReadablePropertyValueCount(
            Object object, boolean recursively, Set<String> ignoredProperties, String parentPath) {
        if (object == null) {
            return null;
        }

        int totalReadablePropertyCount = 0;
        int nonNullValueCount = 0;

        List<Field> fields = getAllDeclaredFields(object);

        for (Field field : fields) {
            String fieldPath = buildFieldPath(parentPath, field);

            if (ignoredProperties != null && ignoredProperties.contains(fieldPath)) {
                continue;
            }

            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(), ignoredProperties, fieldPath);
                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,
                            ignoredProperties, fieldPath);

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

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

    @SneakyThrows
    private static int getReadablePropertyCount(
            Class<?> inspectedClass, Set<String> ignoredProperties, String parentPath) {
        int totalReadablePropertyCount = 0;

        Field[] fields = inspectedClass.getDeclaredFields();

        for (Field field : fields) {
            String fieldPath = buildFieldPath(parentPath, field);

            if (ignoredProperties != null && ignoredProperties.contains(fieldPath)) {
                continue;
            }

            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, ignoredProperties, fieldPath);

                    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);
    }

    private static String buildFieldPath(String parentPath, Field field) {
        return parentPath == null ? field.getName() : parentPath + "." + field.getName();
    }

    @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 java.util.Set;

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);
    }

    @Test
    public void getReadablePropertyValueCount_whenSomeIgnoredProperties() {
        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,
                        Set.of("propertyA", "propertyD.propertyX", "propertyD.nestedProperty.propertyY"));

        assertThat(propertyValueCount).isNotNull();
        assertThat(propertyValueCount.getTotalCount()).isEqualTo(29);
        assertThat(propertyValueCount.getNonNullValueCount()).isEqualTo(19);
    }

    @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

    }

}

完成百分比可以这样计算:

        PropertyCountUtils.PropertyValueCount propertyValueCount = getReadablePropertyValueCount(profile);

        BigDecimal profileCompletionPercentage = BigDecimal.valueOf(propertyValueCount.getNonNullValueCount())
                .multiply(BigDecimal.valueOf(100))
                .divide(BigDecimal.valueOf(propertyValueCount.getTotalCount()), 2, RoundingMode.UP)
                .stripTrailingZeros();

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM