简体   繁体   English

计算对象中的非空字段

[英]Count non null fields in an object

I have a UserProfile class which contains user's data as shown below:我有一个包含用户数据的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
}

I need to count non null fields to show how much percentage of the profile has been filled by the user.我需要计算非null字段以显示用户填写了多少百分比的配置文件。 Also there are few fields which I do not want to consider in percentage calculation like: userId , loginId and displayName .还有一些我不想在百分比计算中考虑的字段,例如: userIdloginIddisplayName

Simple way would be to use multiple If statements to get the non null field count but it would involve lot of boiler plate code and there is another class Organization for which I need to show completion percentage as well.简单的方法是使用多个If语句来获取非空字段count ,但它会涉及大量样板代码,并且还有另一个类Organization我也需要显示完成百分比。 So I created a utility function as show below:所以我创建了一个实用函数,如下所示:

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

And then I call this function as shown below:然后我调用这个函数,如下所示:

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

My question is, is this the best way I could count not null fields or I could improve it further.我的问题是,这是我不能计算null字段的最好方法,还是我可以进一步改进它。 Please suggest.请建议。

You can simply a lot your code by creating a Stream over the given list of functions: 您可以通过在给定的函数列表上创建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();
}

This will return the count of non- null fields returned by each function. 这将返回每个函数返回的非null字段的计数。 Each function is mapped to the result of applying it to the given object and null fields are filtered out with the predicate Objects::nonNull . 每个函数都映射到将其应用于给定对象的结果,并使用谓词Objects::nonNull过滤掉null字段。

I wrote some utility methods to get the total count of readable properties and the count of non null values in an object.我编写了一些实用方法来获取对象中可读属性的总数和非空值的数量。 The completion percentage can be calculated based on these.可以根据这些计算完成百分比。

It should work pretty well with inherited properties and with nested objects too.它应该可以很好地处理继承属性和嵌套对象。 It will probably need some adjustments to inspect the content of collection properties as well.它可能还需要一些调整来检查集合属性的内容。

Here's the utility class:这是实用程序类:

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

And here are some tests for it:这里有一些测试:

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

    }

}

The completion percentage can be calculated like this:完成百分比可以这样计算:

        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