[英]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.