如何在单个Java类中实现Normalizer与ScoringSummary接口?需CSV解析依赖吗?
实现方案说明
一、接口实现逻辑
你可以在同一个类中实现两个接口,不过更推荐按单一职责拆分实现,可维护性更高:
1. 先实现ScoringSummary接口
先写一个不可变的实现类存储统计结果,实现ScoringSummary接口:
import java.math.BigDecimal; public class ScoringSummaryImpl implements ScoringSummary { private final BigDecimal mean; private final BigDecimal standardDeviation; private final BigDecimal variance; private final BigDecimal median; private final BigDecimal min; private final BigDecimal max; public ScoringSummaryImpl(BigDecimal mean, BigDecimal standardDeviation, BigDecimal variance, BigDecimal median, BigDecimal min, BigDecimal max) { this.mean = mean; this.standardDeviation = standardDeviation; this.variance = variance; this.median = median; this.min = min; this.max = max; } @Override public BigDecimal mean() { return mean; } @Override public BigDecimal standardDeviation() { return standardDeviation; } @Override public BigDecimal variance() { return variance; } @Override public BigDecimal median() { return median; } @Override public BigDecimal min() { return min; } @Override public BigDecimal max() { return max; } }
2. 实现Normalizer接口
写CsvNormalizer类实现Normalizer接口,内部处理CSV读写、数值计算逻辑,计算完成后返回ScoringSummaryImpl实例:
import java.math.BigDecimal; import java.math.RoundingMode; import java.nio.file.Path; import java.util.List; public class CsvNormalizer implements Normalizer { private static final int SCALE = 2; private static final RoundingMode ROUNDING_MODE = RoundingMode.HALF_UP; @Override public ScoringSummary zscore(Path csvPath, Path destPath, String colToStandardize) { // 步骤1:读取CSV,提取目标列所有数值 List<BigDecimal> columnValues = readColumnValues(csvPath, colToStandardize); // 步骤2:计算统计指标 BigDecimal mean = calculateMean(columnValues); BigDecimal variance = calculateVariance(columnValues, mean); BigDecimal std = variance.sqrt(ROUNDING_MODE).setScale(SCALE, ROUNDING_MODE); BigDecimal median = calculateMedian(columnValues); BigDecimal min = columnValues.stream().min(BigDecimal::compareTo).orElseThrow(); BigDecimal max = columnValues.stream().max(BigDecimal::compareTo).orElseThrow(); // 步骤3:逐行计算Z-Score,写入新CSV writeScaledCsv(csvPath, destPath, colToStandardize, val -> val.subtract(mean).divide(std, SCALE, ROUNDING_MODE), "z_score" ); // 封装返回统计结果 return new ScoringSummaryImpl( mean.setScale(SCALE, ROUNDING_MODE), std, variance.setScale(SCALE, ROUNDING_MODE), median.setScale(SCALE, ROUNDING_MODE), min.setScale(SCALE, ROUNDING_MODE), max.setScale(SCALE, ROUNDING_MODE) ); } @Override public ScoringSummary minMaxScaling(Path csvPath, Path destPath, String colToNormalize) { // 逻辑和zscore类似,仅计算公式替换为 (x-min)/(max-min) 即可 List<BigDecimal> columnValues = readColumnValues(csvPath, colToNormalize); BigDecimal mean = calculateMean(columnValues); BigDecimal variance = calculateVariance(columnValues, mean); BigDecimal std = variance.sqrt(ROUNDING_MODE).setScale(SCALE, ROUNDING_MODE); BigDecimal median = calculateMedian(columnValues); BigDecimal min = columnValues.stream().min(BigDecimal::compareTo).orElseThrow(); BigDecimal max = columnValues.stream().max(BigDecimal::compareTo).orElseThrow(); BigDecimal range = max.subtract(min); writeScaledCsv(csvPath, destPath, colToNormalize, val -> val.subtract(min).divide(range, SCALE, ROUNDING_MODE), "min_max_score" ); return new ScoringSummaryImpl( mean.setScale(SCALE, ROUNDING_MODE), std, variance.setScale(SCALE, ROUNDING_MODE), median.setScale(SCALE, ROUNDING_MODE), min.setScale(SCALE, ROUNDING_MODE), max.setScale(SCALE, ROUNDING_MODE) ); } // 以下是私有工具方法,实现读取CSV列、计算统计值、写入CSV的逻辑 private List<BigDecimal> readColumnValues(Path csvPath, String columnName) { // 实现CSV读取,提取目标列的数值 } private BigDecimal calculateMean(List<BigDecimal> values) { BigDecimal sum = values.stream().reduce(BigDecimal.ZERO, BigDecimal::add); return sum.divide(BigDecimal.valueOf(values.size()), SCALE, ROUNDING_MODE); } private BigDecimal calculateVariance(List<BigDecimal> values, BigDecimal mean) { BigDecimal sumSquareDiff = values.stream() .map(val -> val.subtract(mean).pow(2)) .reduce(BigDecimal.ZERO, BigDecimal::add); return sumSquareDiff.divide(BigDecimal.valueOf(values.size()), SCALE, ROUNDING_MODE); } private BigDecimal calculateMedian(List<BigDecimal> values) { List<BigDecimal> sorted = values.stream().sorted().toList(); int size = sorted.size(); if (size % 2 == 1) { return sorted.get(size / 2); } else { return sorted.get(size / 2 - 1).add(sorted.get(size / 2)) .divide(BigDecimal.valueOf(2), SCALE, ROUNDING_MODE); } } private void writeScaledCsv(Path csvPath, Path destPath, String targetCol, java.util.function.Function<BigDecimal, BigDecimal> scoreCalculator, String newColName) { // 实现CSV逐行读取,计算得分后写入新文件 } }
如果你硬要把两个接口的实现放到同一个类中,只需要让类同时implements Normalizer, ScoringSummary即可,但是需要注意统计值的存储时机,不符合单一职责原则,不推荐这么做。
二、CSV解析依赖说明
你不需要引入重型框架,有两种方案可选:
- 推荐引入轻量CSV处理库OpenCSV,避免手动处理CSV的分隔符转义、字段含引号/换行、空值等边界问题,Maven依赖如下:
<dependency> <groupId>com.opencsv</groupId> <artifactId>opencsv</artifactId> <version>5.6</version> </dependency>
- 如果不想引入第三方依赖,也可以用Java原生的
BufferedReader逐行读取按逗号拆分,仅适合CSV格式非常规范、无特殊字符的场景。
内容的提问来源于stack exchange,提问作者Shahed Abd
相关产品推荐
相关产品推荐

