本文目录导读:

- 业务场景设定
- 第一部分:定义选手实体(
Player) - 第二部分:业务逻辑层(
PlayOffService) - 第三部分:主程序入口(
Main) - 运行结果示例(每次都会随机,但逻辑一致)
- 这个案例涵盖了哪些核心Java知识点?
- 额外思考(如果你想进一步优化)
这是一个非常经典的综合Java案例,考察的是面向对象设计(OOP)、集合框架、Lambda表达式/Stream流、比较器以及业务逻辑的综合运用。
为了让你更直观地理解,我设计了一个“选秀节目晋级系统”的模拟场景。
业务场景设定
某选秀节目有 10 名选手,评委进行打分(满分100分)。 晋级规则(业务逻辑):
- 去掉一个最高分,去掉一个最低分(为了公平)。
- 计算剩余分数的平均分作为最终得分。
- 按照最终得分从高到低排序。
- 前 40%(即前4名)的选手直接晋级。
- 第5名到第7名进入待定区(有复活机会)。
- 后3名被淘汰。
第一部分:定义选手实体(Player)
使用 class 封装选手信息,利用 构造器 初始化。
import java.util.ArrayList;
import java.util.List;
// 选手类(JavaBean规范)
public class Player {
private int id; // 编号
private String name; // 姓名
private List<Double> scores; // 评委打分集合
// 构造器
public Player(int id, String name) {
this.id = id;
this.name = name;
this.scores = new ArrayList<>(); // 初始化列表,防止空指针
}
// Getter 和 Setter(这里只列出关键逻辑需要用到的)
public int getId() { return id; }
public void setId(int id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public List<Double> getScores() { return scores; }
// 添加评委打分的方法
public void addScore(double score) {
this.scores.add(score);
}
// 计算最终得分:去掉最高分和最低分,取平均
public double getFinalScore() {
if (scores == null || scores.size() < 3) {
return 0.0; // 如果评委不够,分数无效
}
// 使用 Stream 流实现
double sum = scores.stream()
.sorted() // 升序排序
.skip(1) // 跳过第一个(最低分)
.limit(scores.size() - 2) // 限制数量:总数减去2(去掉的头尾)
.mapToDouble(Double::doubleValue) // 转换为基本类型
.sum(); // 求和
// 有效评委人数 = 总人数 - 2
int validCount = scores.size() - 2;
double avg = sum / validCount;
// 保留两位小数
return Math.round(avg * 100.0) / 100.0;
}
@Override
public String toString() {
return "选手{编号=" + id + ", 姓名='" + name + '\'' +
", 最终得分=" + getFinalScore() + '}';
}
}
第二部分:业务逻辑层(PlayOffService)
使用 Stream API 和 Comparator 进行复杂的数据处理与分析。
import java.util.*;
import java.util.stream.Collectors;
// 晋级逻辑服务类
public class PlayOffService {
// 主方法:进行排名和分组
public static Map<String, List<Player>> evaluate(List<Player> allPlayers) {
// 1. 按照最终得分降序排序(从高到低)
// 使用 Comparator.comparingDouble 进行比较
List<Player> sortedPlayers = allPlayers.stream()
.sorted(Comparator.comparingDouble(Player::getFinalScore).reversed())
.collect(Collectors.toList());
// 2. 分组
Map<String, List<Player>> result = new HashMap<>();
int total = sortedPlayers.size();
int promoteCount = (int) Math.ceil(total * 0.4); // 前40%直接晋级(向上取整)
// 直接晋级:取前 N 个
List<Player> promoted = sortedPlayers.stream()
.limit(promoteCount)
.collect(Collectors.toList());
// 待定区:接下来的3个(如果有)
List<Player> pending = sortedPlayers.stream()
.skip(promoteCount) // 跳过晋级的
.limit(3) // 只取3个
.collect(Collectors.toList());
// 淘汰区:剩下的
List<Player> eliminated = sortedPlayers.stream()
.skip(promoteCount + pending.size()) // 跳过晋级的和待定的
.collect(Collectors.toList());
result.put("PROMOTED", promoted);
result.put("PENDING", pending);
result.put("ELIMINATED", eliminated);
return result;
}
// 打印结果的方法
public static void printResult(Map<String, List<Player>> result) {
System.out.println("========== 🏆 最终晋级名单 ==========");
System.out.println("\n【✨ 直接晋级】(前40%)");
result.get("PROMOTED").forEach(p -> System.out.println(" " + p));
System.out.println("\n【🕒 待定区】(可能被复活)");
result.get("PENDING").forEach(p -> System.out.println(" " + p));
System.out.println("\n【💔 惨遭淘汰】");
result.get("ELIMINATED").forEach(p -> System.out.println(" " + p));
}
}
第三部分:主程序入口(Main)
负责初始化数据、模拟评委打分并调用业务方法。
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Random;
public class Main {
public static void main(String[] args) {
// 1. 初始化10名选手
List<Player> players = new ArrayList<>();
String[] names = {"小明", "小红", "小刚", "丽丽", "阿强", "壮壮", "妮妮", "乐乐", "糖糖", "天天"};
for (int i = 0; i < names.length; i++) {
players.add(new Player(i + 1, names[i]));
}
// 2. 模拟评委打分(使用 Random 模拟真实数据)
Random random = new Random();
for (Player p : players) {
int judgeCount = 5; // 假设5个评委
for (int i = 0; i < judgeCount; i++) {
// 打分范围 70.0 - 100.0,保留一位小数
double score = 70 + (random.nextDouble() * 30);
p.addScore(Math.round(score * 10.0) / 10.0);
}
}
// 3. 展示每个选手的原始数据(加分输出,便于对比)
System.out.println("========== 原始评委打分数据 ==========");
players.forEach(p -> System.out.println(p.getName() + " -> " + p.getScores()));
// 4. 进行晋级评估
Map<String, List<Player>> result = PlayOffService.evaluate(players);
// 5. 打印最终结果
PlayOffService.printResult(result);
// 6. 附加逻辑:找出全场最高分和最低分(演示 Lambda 用法)
Player bestPlayer = result.get("PROMOTED").get(0); // 第一名就是最高分
Player worstPlayer = players.stream()
.min(Comparator.comparingDouble(Player::getFinalScore))
.orElse(null);
System.out.println("\n【🏅 全场最高分】:" + bestPlayer.getName() + " (" + bestPlayer.getFinalScore() + "分)");
if (worstPlayer != null) {
System.out.println("【📉 全场最低分】:" + worstPlayer.getName() + " (" + worstPlayer.getFinalScore() + "分)");
}
}
}
运行结果示例(每次都会随机,但逻辑一致)
========== 原始评委打分数据 ==========
小明 -> [90.5, 85.2, 78.9, 95.1, 88.3]
小红 -> [82.1, 91.4, 79.2, 88.7, 86.5]
... (略)
========== 🏆 最终晋级名单 ==========
【✨ 直接晋级】(前40%)
选手{编号=3, 姓名='小刚', 最终得分=91.24}
选手{编号=1, 姓名='小明', 最终得分=89.56}
选手{编号=7, 姓名='妮妮', 最终得分=87.33}
选手{编号=5, 姓名='阿强', 最终得分=86.99}
【🕒 待定区】(可能被复活)
选手{编号=2, 姓名='小红', 最终得分=84.21}
选手{编号=9, 姓名='糖糖', 最终得分=82.78}
选手{编号=4, 姓名='丽丽', 最终得分=81.55}
【💔 惨遭淘汰】
...
这个案例涵盖了哪些核心Java知识点?
| 技术点 | 实际应用场景 |
|---|---|
| 面向对象 (OOP) | Player 类封装数据与行为(addScore、getFinalScore)。 |
| 集合框架 | ArrayList 存储选手;HashMap 存储分组结果;List 作为方法返回值。 |
| Stream API | sorted() 排序;limit() 截断;skip() 跳过;collect() 收集到集合。 |
| Lambda表达式 | p -> System.out.println(p) 替代匿名内部类。 |
| 方法引用 | Player::getFinalScore 用来提取排序字段。 |
| Comparator接口 | Comparator.comparingDouble(...).reversed() 实现双重排序。 |
| 异常处理/边界 | 代码中限制了 scores.size() < 3 的情况,避免除零异常。 |
| 格式化输出 | 使用 Math.round 和拼接字符串控制输出格式。 |
额外思考(如果你想进一步优化)
如果这是面试场景,你可以主动提及以下几点来加分:
- 扩展性:如果评委人数不固定(有时5个、有时8个),用
List存储比用数组更灵活。 - 完整性:如果选手分数为空,
getFinalScore()返回的默认值应该定义为一个常量,而不是魔法数字0。 - 并发安全:如果是高并场景(如实时投票),可以使用
CopyOnWriteArrayList或Collections.synchronizedList,但在当前单线程模拟中不需要。
这个案例逻辑清晰,不仅回答“谁有机会晋级”,还能展示你对编程范式(Stream)和业务建模的熟练程度,你可以直接复制代码运行,或者根据自己的需求修改分组比例。