
本文讲解如何从文本文件中读取拳击选手数据,按体重级别(如 flyweight、featherweight)分类,并分别提取各组最高分选手——使用 Java 8+ Stream API 和面向对象设计实现高效、可维护的解决方案。
本文讲解如何从文本文件中读取拳击选手数据,按体重级别(如 flyweight、featherweight)分类,并分别提取各组最高分选手——使用 Java 8+ Stream API 和面向对象设计实现高效、可维护的解决方案。
在实际开发中,从外部文件(如 boxingcompetition.txt)动态构建结构化数据是常见需求。与其手动维护多个独立数组(如 flyweightScores[] 和 featherweightScores[]),更推荐采用面向对象建模 + 集合分组 + 函数式处理的方式,兼顾可扩展性与代码清晰度。
核心思路
- 定义领域模型:创建 Boxer 类封装姓名、体重级别(division)、得分;
- 统一解析输入:逐行读取 CSV 格式数据,构造 Boxer 对象并存入 List<Boxer>;
- 按类别分组:利用 Collectors.groupingBy(Boxer::getDivision) 将选手按 division 聚合成 Map<String, List<Boxer>>;
- 每组取最优解:对每个体重级别的 List<Boxer> 调用 Collections.max() 获取最高分选手(需实现 Comparable 或传入 Comparator)。
关键实现细节
✅ Boxer 必须可比较:compareTo() 方法应按 score 降序排列(返回 other.score - this.score 实现升序;当前示例中 this.score - other.score 实际为升序,但输出结果表明逻辑已适配最大值获取——更稳妥写法是显式使用 Comparator.comparingInt(Boxer::getScore).reversed()):
// 推荐写法:明确语义,避免歧义
@Override
public int compareTo(Boxer other) {
return Integer.compare(other.score, this.score); // 降序:高分在前
}✅ 健壮的文件读取:使用 getResourceAsStream() 加载 classpath 下资源(如 src/main/resources/boxingcompetition.txt),注意首行 competition 数值仅作提示,真实数据行数由文件内容决定,不应硬编码循环次数(原问题中 totalCompetition = competition * 2 易出错,应以 hasNextLine() 动态判断)。
✅ 分组后提取每类 Top 1:
Map<String, List<Boxer>> grouped = boxers.stream()
.collect(Collectors.groupingBy(Boxer::getDivision));
List<Boxer> topPerDivision = grouped.values().stream()
.map(list -> list.stream()
.max(Comparator.comparingInt(Boxer::getScore))
.orElse(null)) // 处理空列表边界情况
.filter(Objects::nonNull)
.collect(Collectors.toList());完整可运行示例(精简版)
import java.io.*;
import java.net.URL;
import java.util.*;
import java.util.stream.Collectors;
public class BoxingAnalyzer {
public static void main(String[] args) {
List<Boxer> allBoxers = parseFile("/boxingcompetition.txt");
// 按体重级别分组,并取每组最高分选手
Map<String, Boxer> topByDivision = allBoxers.stream()
.collect(Collectors.toMap(
Boxer::getDivision,
boxer -> boxer,
(b1, b2) -> b1.getScore() >= b2.getScore() ? b1 : b2 // 同级别保留高分者
));
topByDivision.forEach((div, boxer) ->
System.out.printf("%s: %s (%d points)%n", div, boxer.getName(), boxer.getScore())
);
}
private static List<Boxer> parseFile(String path) {
List<Boxer> boxers = new ArrayList<>();
try (Scanner scan = new Scanner(Objects.requireNonNull(
BoxingAnalyzer.class.getResourceAsStream(path)))) {
if (scan.hasNextLine()) scan.nextLine(); // 跳过首行 competition 计数(非必要)
while (scan.hasNextLine()) {
String line = scan.nextLine().trim();
if (line.isEmpty()) continue;
String[] parts = line.split("\\s*,\\s*"); // 更鲁棒的逗号分割(忽略空格)
if (parts.length < 3) continue;
boxers.add(new Boxer(
parts[0].trim(),
parts[2].trim(),
Integer.parseInt(parts[1].trim())
));
}
} catch (Exception e) {
throw new RuntimeException("Failed to read boxing data", e);
}
return boxers;
}
private static class Boxer {
private final String name, division;
private final int score;
Boxer(String name, String division, int score) {
this.name = name;
this.division = division;
this.score = score;
}
String getName() { return name; }
String getDivision() { return division; }
int getScore() { return score; }
@Override
public String toString() {
return String.format("%s (%s): %d", name, division, score);
}
}
}注意事项
- ❗ 文件路径必须位于 resources/ 目录下,且确保构建工具(Maven/Gradle)将其复制到 classpath;
- ❗ 输入格式需严格匹配:姓名,分数,级别,避免多余空格或缺失字段(建议增加 try-catch 和日志提升容错性);
- ✅ 若需返回原始数组(如 Boxer[]),可对结果调用 .toArray(new Boxer[0]);
- ✅ 后续扩展(如求平均分、统计人数)只需修改 Stream 管道,无需重构核心结构。
通过该方案,你不仅解决了“为不同级别创建数组”的表层需求,更建立了可复用的数据处理骨架——未来新增 lightweight 或 heavyweight 类别,代码零修改即可支持。