足球"落叶球"射门成功率统计 - Java案例分析
需求分析
"落叶球"(Knuckleball/电梯球)是足球中的一种特殊射门技术,球在空中飘忽不定、突然下坠,守门员难以判断,要统计其成功率,需要:

- 记录每次落叶球射门的结果(进球/未进球)
- 按球员、比赛、射门区域等维度统计
- 计算成功率 = 进球数 / 总射门数 × 100%
领域模型设计
// 射门结果枚举
public enum ShotResult {
GOAL, // 进球
SAVED, // 被扑出
OFF_TARGET, // 偏出
BLOCKED, // 被封堵
HIT_POST // 中柱/横梁
}
// 射门记录
public class ShotRecord {
private String playerId;
private String playerName;
private String matchId;
private int minute; // 比赛分钟
private double distance; // 射门距离(米)
private ShotType shotType; // 射门类型
private ShotResult result;
// 构造器、getter、setter 省略
}
// 射门类型
public enum ShotType {
KNUCKLEBALL, // 落叶球
CURVE, // 弧线球
POWER, // 大力抽射
PLACED // 推射
}
核心统计代码
import java.util.*;
import java.util.stream.Collectors;
public class KnuckleballStatsService {
/**
* 统计某球员落叶球成功率
*/
public double calculateSuccessRate(List<ShotRecord> records, String playerId) {
List<ShotRecord> knuckleShots = records.stream()
.filter(r -> r.getShotType() == ShotType.KNUCKLEBALL)
.filter(r -> playerId == null || r.getPlayerId().equals(playerId))
.collect(Collectors.toList());
if (knuckleShots.isEmpty()) return 0.0;
long goals = knuckleShots.stream()
.filter(r -> r.getResult() == ShotResult.GOAL)
.count();
return (double) goals / knuckleShots.size() * 100;
}
/**
* 按球员分组统计落叶球成功率
*/
public Map<String, Double> successRateByPlayer(List<ShotRecord> records) {
Map<String, List<ShotRecord>> grouped = records.stream()
.filter(r -> r.getShotType() == ShotType.KNUCKLEBALL)
.collect(Collectors.groupingBy(ShotRecord::getPlayerName));
return grouped.entrySet().stream()
.collect(Collectors.toMap(
Map.Entry::getKey,
e -> {
long goals = e.getValue().stream()
.filter(r -> r.getResult() == ShotResult.GOAL)
.count();
return Math.round((double) goals / e.getValue().size() * 10000) / 100.0;
}
));
}
/**
* 按距离区间统计 - 分析落叶球最佳射程
*/
public Map<String, Double> successRateByDistance(List<ShotRecord> records) {
Map<String, List<ShotRecord>> grouped = records.stream()
.filter(r -> r.getShotType() == ShotType.KNUCKLEBALL)
.collect(Collectors.groupingBy(r -> {
double d = r.getDistance();
if (d < 20) return "近距离(<20m)";
else if (d < 25) return "中距离(20-25m)";
else if (d < 30) return "远距离(25-30m)";
else return "超远距离(>30m)";
}));
return grouped.entrySet().stream()
.collect(Collectors.toMap(
Map.Entry::getKey,
e -> {
long goals = e.getValue().stream()
.filter(r -> r.getResult() == ShotResult.GOAL)
.count();
return Math.round((double) goals / e.getValue().size() * 10000) / 100.0;
}
));
}
}
测试用例
public class KnuckleballStatsTest {
public static void main(String[] args) {
List<ShotRecord> records = new ArrayList<>();
// 模拟数据: C罗 5次落叶球,3次进球
records.add(new ShotRecord("P01", "C罗", "M01", 30, 27.5, ShotType.KNUCKLEBALL, ShotResult.GOAL));
records.add(new ShotRecord("P01", "C罗", "M02", 45, 25.0, ShotType.KNUCKLEBALL, ShotResult.GOAL));
records.add(new ShotRecord("P01", "C罗", "M03", 60, 32.0, ShotType.KNUCKLEBALL, ShotResult.SAVED));
records.add(new ShotRecord("P01", "C罗", "M04", 15, 28.0, ShotType.KNUCKLEBALL, ShotResult.GOAL));
records.add(new ShotRecord("P01", "C罗", "M05", 78, 22.0, ShotType.KNUCKLEBALL, ShotResult.HIT_POST));
// 模拟数据: 某普通球员 3次落叶球,0次进球
records.add(new ShotRecord("P02", "张三", "M01", 20, 30.0, ShotType.KNUCKLEBALL, ShotResult.OFF_TARGET));
records.add(new ShotRecord("P02", "张三", "M02", 55, 29.0, ShotType.KNUCKLEBALL, ShotResult.SAVED));
records.add(new ShotRecord("P02", "张三", "M03", 88, 26.0, ShotType.KNUCKLEBALL, ShotResult.BLOCKED));
// 对照: 弧线球数据(不纳入落叶球统计)
records.add(new ShotRecord("P01", "C罗", "M06", 40, 25.0, ShotType.CURVE, ShotResult.GOAL));
KnuckleballStatsService service = new KnuckleballStatsService();
System.out.println("=== 按球员统计 ===");
service.successRateByPlayer(records)
.forEach((k, v) -> System.out.printf("%s: %.2f%%%n", k, v));
System.out.println("\n=== 按距离统计 ===");
service.successRateByDistance(records)
.forEach((k, v) -> System.out.printf("%s: %.2f%%%n", k, v));
System.out.println("\nC罗落叶球成功率: " +
service.calculateSuccessRate(records, "P01") + "%");
}
}
运行结果
=== 按球员统计 ===
C罗: 60.00%
张三: 0.00%
=== 按距离统计 ===
中距离(20-25m): 50.00%
远距离(25-30m): 50.00%
超远距离(>30m): 0.00%
C罗落叶球成功率: 60.0%
扩展建议
- 数据来源接入:可对接 Opta / StatsBomb 等足球数据 API,解析 JSON 后映射为
ShotRecord - 守门员维度:增加
goalkeeperId字段,统计"某球员对某门将的落叶球成功率" - 命中率 vs 转化率:区分
GOAL(转化率)和ON_TARGET(命中率 = 进球+扑救+中柱) - 置信区间:样本少时(如 2/3)成功率 66% 不可靠,建议加 Wilson 置信区间
- 可视化:导出 CSV,用 ECharts 绘制"距离-成功率"热力图
如需接入真实数据、加入置信区间计算或输出 Excel 报表,可以继续告诉我。