如何用Java泛型循环批量实现POJO字段的归一化赋值?
问题描述
我的POJO结构如下:
@Builder(toBuilder = true) @Getter public final class TestPOJO { private double fieldA; private double normFieldA; private double fieldB; private double normFieldB; private double fieldC; private double normFieldC; private double fieldD; private double normFieldD; private double fieldE; private double normFieldE; }
我有一个TestPOJO列表,需要为每个字段计算归一化值,当前实现函数如下:
List<TestPOJO> setNormalizedValue(List<TestPOJO> pojos) { final double[][] rawValues = new double[5][pojos.size()]; int i = 0; for (TestPOJO pojo: pojos) { rawValues[0][i] = pojo.getFieldA(); rawValues[1][i] = pojo.getFieldB(); rawValues[2][i] = pojo.getFieldC(); rawValues[3][i] = pojo.getFieldD(); rawValues[4][i] = pojo.getFieldE(); i++; } final Pair<double[], double[]> stdPopAndMean = getStdPopAndMean(rawValues, pojos.size()); final double[] stdPop = stdPopAndMean.getLeft(); final double[] mean = stdPopAndMean.getRight(); return pojos.stream() .map(p -> p.toBuilder() .normFieldA(getNormalizedValue(p.getFieldA(), stdPop[0], mean[0])) .normFieldB(getNormalizedValue(p.getFieldB(), stdPop[1], mean[1])) .normFieldC(getNormalizedValue(p.getFieldC(), stdPop[2], mean[2])) .normFieldD(getNormalizedValue(p.getFieldD(), stdPop[3], mean[3])) .normFieldE(getNormalizedValue(p.getFieldE(), stdPop[4], mean[4])) .build() ) .collect(Collectors.toList()); }
但实际我有大约20个字段,而非示例中的5个。是否可以通过Java泛型循环来实现?我尝试使用Supplier和Consumer,但遇到了Cannot refer to static content from non-static.的错误。
我原本的思路如下:
private static final Map<Integer, Pair<Consumer<Double>, Supplier<Double>>> FUNCTIONS_MAP = ImmutableMap.<Integer, Pair<Consumer<Double>, Supplier<Double>>>builder() .put(0, Pair.of(TestPOJO.TestPOJOBuilder::normFieldA, TestPOJO::getFieldA)) .put(1, Pair.of(TestPOJO.TestPOJOBuilder::normFieldB, TestPOJO::getFieldB)) .put(2, Pair.of(TestPOJO.TestPOJOBuilder::normFieldC, TestPOJO::getFieldC)) .put(3, Pair.of(TestPOJO.TestPOJOBuilder::normFieldD, TestPOJO::getFieldD)) .put(4, Pair.of(TestPOJO.TestPOJOBuilder::normFieldE, TestPOJO::getFieldE)) .build(); List<TestPOJO> setNormalizedValue(List<TestPOJO> pojos) { double[][] rawValues = new double[FUNCTIONS_MAP.size()][pojos.size()]; int i = 0; for (TestPOJO pojo: pojos) { for (Map.Entry<Integer, Pair<Consumer<Double>, Supplier<Double>>> entry : FUNCTIONS_MAP.entrySet()) { final int position = entry.getKey(); final Supplier<Double> getter = entry.getValue().getRight(); rawValues[position][i] = getter.get(); // 此处Supplier无法接收pojo参数 } i++; } final Pair<double[], double[]> stdPopAndMean = getStdPopAndMean(rawValues, pojos.size()); final double[] stdPop = stdPopAndMean.getLeft(); final double[] mean = stdPopAndMean.getRight(); return pojos.stream() .map(p -> { TestPOJO.TestPOJOBuilder builder = p.toBuilder(); for (Map.Entry<Integer, Pair<Consumer<Double>, Supplier<Double>>> entry : FUNCTIONS_MAP.entrySet()) { final int position = entry.getKey(); final Supplier<Double> getter = entry.getValue().getRight(); final Consumer<Double> setter = entry.getValue().getLeft(); final double similarity = getNormalizedValue(getter.get(p), stdPop[position], mean[position]); // Supplier无法接收参数 setter.accept(similarity); // Consumer无法关联到builder } return builder.build(); } ) .collect(Collectors.toList()); }
显然这个实现存在问题,我相信有更优的解决方案,请问如何通过循环来实现该功能?
我知道可以通过反射实现,但我不想使用反射。
补充的工具方法:
/** * 计算给定二维数组的均值和总体标准差。 * 将每个一维数组视为一个数据集,计算其均值和标准差。 * * @param values 待计算的二维数组。 * @param recordCount 一维数组的长度,假设所有行长度相同。 * @return 包含标准差数组和均值数组的Pair,每个元素对应每行的计算结果。 */ public Pair<double[], double[]> getStdPopAndMean(final double[][] values, final int recordCount) { final double[] mean = new double[values.length]; final double[] stdPop = new double[values.length]; for (int i = 0; i < values.length; i++) { mean[i] = getMean(values[i], recordCount); } for (int i = 0; i < values.length; i++) { double diffSquared = 0; for (int j = 0; j < recordCount && j < values[i].length; j++) { diffSquared += (values[i][j] - mean[i]) * (values[i][j] - mean[i]); } final double variance = diffSquared / recordCount; stdPop[i] = FastMath.sqrt(variance); } return Pair.of(stdPop, mean); }
解决方案
核心问题是误用了Supplier和Consumer——它们无法携带目标对象(POJO或Builder)的上下文。正确做法是定义自定义元数据类,封装每个字段的getter(从POJO取原始值)、setter(给Builder设归一化值),再通过循环遍历这些元数据实现批量处理。
步骤1:定义字段元数据类
创建类封装字段的操作逻辑:
@AllArgsConstructor public class FieldMetadata { private final Function<TestPOJO, Double> rawValueGetter; private final BiConsumer<TestPOJO.TestPOJOBuilder, Double> normalizedValueSetter; // 获取原始值 public double getRawValue(TestPOJO pojo) { return rawValueGetter.apply(pojo); } // 设置归一化值到Builder public void setNormalizedValue(TestPOJO.TestPOJOBuilder builder, double value) { normalizedValueSetter.accept(builder, value); } }
步骤2:初始化字段元数据列表
将所有需要处理的字段对应的getter和setter放入列表:
private static final List<FieldMetadata> FIELD_METADATA_LIST = List.of( new FieldMetadata(TestPOJO::getFieldA, TestPOJO.TestPOJOBuilder::normFieldA), new FieldMetadata(TestPOJO::getFieldB, TestPOJO.TestPOJOBuilder::normFieldB), new FieldMetadata(TestPOJO::getFieldC, TestPOJO.TestPOJOBuilder::normFieldC), new FieldMetadata(TestPOJO::getFieldD, TestPOJO.TestPOJOBuilder::normFieldD), new FieldMetadata(TestPOJO::getFieldE, TestPOJO.TestPOJOBuilder::normFieldE) // 继续添加剩余15个字段... );
步骤3:重构批量处理逻辑
用元数据列表替代硬编码的字段操作,实现循环处理:
List<TestPOJO> setNormalizedValue(List<TestPOJO> pojos) { int fieldCount = FIELD_METADATA_LIST.size(); int pojoCount = pojos.size(); double[][] rawValues = new double[fieldCount][pojoCount]; // 收集所有原始值 for (int pojoIdx = 0; pojoIdx < pojoCount; pojoIdx++) { TestPOJO pojo = pojos.get(pojoIdx); for (int fieldIdx = 0; fieldIdx < fieldCount; fieldIdx++) { rawValues[fieldIdx][pojoIdx] = FIELD_METADATA_LIST.get(fieldIdx).getRawValue(pojo); } } // 计算均值和标准差 Pair<double[], double[]> stdPopAndMean = getStdPopAndMean(rawValues, pojoCount); double[] stdPop = stdPopAndMean.getLeft(); double[] mean = stdPopAndMean.getRight(); // 批量设置归一化值 return pojos.stream() .map(pojo -> { TestPOJO.TestPOJOBuilder builder = pojo.toBuilder(); for (int fieldIdx = 0; fieldIdx < fieldCount; fieldIdx++) { FieldMetadata metadata = FIELD_METADATA_LIST.get(fieldIdx); double rawValue = metadata.getRawValue(pojo); double normalizedValue = getNormalizedValue(rawValue, stdPop[fieldIdx], mean[fieldIdx]); metadata.setNormalizedValue(builder, normalizedValue); } return builder.build(); }) .collect(Collectors.toList()); }
方案优势
Function<TestPOJO, Double>:接收TestPOJO实例并返回原始字段值,替代了无法传参的Supplier。BiConsumer<TestPOJO.TestPOJOBuilder, Double>:接收Builder实例和归一化值完成设置,解决了原方案中Consumer无法关联具体Builder的问题。- 扩展简单:新增字段只需在
FIELD_METADATA_LIST中添加对应元数据,无需修改核心逻辑。
内容的提问来源于stack exchange,提问作者Belphegor21
相关产品推荐
相关产品推荐

