[英]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
.还有一些我不想在百分比计算中考虑的字段,例如:
userId
、 loginId
和displayName
。
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.