[英]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
字段以显示用户填写了多少百分比的配置文件。 还有一些我不想在百分比计算中考虑的字段,例如: userId
、 loginId
和displayName
。
简单的方法是使用多个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.