You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Java中实现生成特殊可打印字符串的Collector并添加表头?

问题描述

需要实现方法:

public Collector<CourseResult, ?, String> printableStringCollector() {

该方法需返回一个Collector,用于生成特殊的可打印多行表格字符串,表格需满足以下要求:

  • 必须包含表头;
  • 列宽根据对应值的长度自适应;
  • 行按学生姓氏排序;
  • 列按任务名称排序;
  • 双精度值保留两位小数显示;
  • “Mark”列的值由总分平均值确定;
  • 注意不同列的对齐方式。

目前已编写如下代码:

public Collector<CourseResult, ?, String> printableStringCollector() {
    return Collector.of(

        StringBuilder::new,
        (sb, courseResult) -> {
            Person person = courseResult.getPerson();
            Map<String, Integer> taskResults = courseResult.getTaskResults();

            List<String> taskNames = taskResults.keySet().stream().sorted().collect(Collectors.toList());

            // 计算总分和平均分
            int totalScore = taskResults.values().stream().reduce(0, Integer::sum);
            double averageMark = (double) totalScore / taskResults.size();
            // 将格式化后的行追加到StringBuilder
            sb.append(String.format("%-16s", person.getLastName() + " " + person.getFirstName()));
            for (String taskName : taskNames) {
                sb.append(String.format("|%-12s", taskResults.getOrDefault(taskName, 0)));
            }
            sb.append(String.format("|%-10.2f|%-5s|\n", averageMark, calculateMark(averageMark)));
        },

        // 合并器:通过拼接内容合并两个StringBuilder
        (sb1, sb2) -> sb1.append(sb2.toString()),

        // 完成器:返回最终格式化的表格字符串
        StringBuilder::toString);
}

private String calculateMark(double avg) {
    return avg > 90 ? "A" 
        : avg >= 83 ? "B" 
            : avg >= 75 ? "C" 
                : avg >= 68 ? "D" 
                    : avg >= 60 ? "E" 
                        : "F";
}

请教如何添加包含“Student”和任务名的表头?示例表格如下:

Student........|Phalaxing |Shieldwalling |Tercioing |Wedging |Total |Mark |
Eco Betty......|0 ........|83............|89........|59......|57.75 |F ...|
Lodbrok Johnny |61 .......|92............|67........|0.......|55.00 |F....|
Paige Umberto..|75....... |94............|0.........|52......|55.25 |F....|
Average........|45.55.... |89.67.........|52.00.....|37.00...|56.00 |F....|
解决方案

你的现有代码是逐条处理CourseResult并拼接行,但要生成符合要求的表头和完整表格,需要先收集全量数据,统一处理任务列、排序、列宽计算、平均值等逻辑。可以重构代码,将容器换成列表来收集所有结果,再在完成器阶段生成完整表格:

修改后的代码如下:

import java.util.*;
import java.util.stream.Collectors;

public Collector<CourseResult, ?, String> printableStringCollector() {
    // 先收集所有CourseResult到列表,后续统一处理
    return Collector.of(
        ArrayList<CourseResult>::new,
        List::add,
        (list1, list2) -> {
            list1.addAll(list2);
            return list1;
        },
        courseResults -> {
            // 1. 收集所有任务名称并排序
            Set<String> allTaskNames = new TreeSet<>();
            for (CourseResult cr : courseResults) {
                allTaskNames.addAll(cr.getTaskResults().keySet());
            }
            List<String> sortedTaskNames = new ArrayList<>(allTaskNames);

            // 2. 按学生姓氏排序行
            List<CourseResult> sortedResults = courseResults.stream()
                    .sorted(Comparator.comparing(cr -> cr.getPerson().getLastName()))
                    .collect(Collectors.toList());

            // 3. 计算各任务的平均分,以及整体总平均分
            Map<String, Double> taskAverages = new HashMap<>();
            double totalAverage = 0.0;
            for (String task : sortedTaskNames) {
                double sum = courseResults.stream()
                        .mapToInt(cr -> cr.getTaskResults().getOrDefault(task, 0))
                        .sum();
                taskAverages.put(task, sum / courseResults.size());
            }
            if (!courseResults.isEmpty()) {
                totalAverage = courseResults.stream()
                        .mapToDouble(cr -> {
                            int total = cr.getTaskResults().values().stream().mapToInt(Integer::intValue).sum();
                            return (double) total / cr.getTaskResults().size();
                        })
                        .average()
                        .orElse(0.0);
            }

            // 4. 计算各列的自适应宽度
            int studentColWidth = Math.max("Student".length(),
                    sortedResults.stream()
                            .map(cr -> cr.getPerson().getLastName() + " " + cr.getPerson().getFirstName())
                            .mapToInt(String::length)
                            .max()
                            .orElse("Student".length()));
            // 任务列宽度:取任务名长度、分数字符串长度、平均值字符串长度的最大值
            Map<String, Integer> taskColWidths = new HashMap<>();
            for (String task : sortedTaskNames) {
                int nameLen = task.length();
                int scoreLen = sortedResults.stream()
                        .map(cr -> String.valueOf(cr.getTaskResults().getOrDefault(task, 0)))
                        .mapToInt(String::length)
                        .max()
                        .orElse(nameLen);
                int avgLen = String.format("%.2f", taskAverages.get(task)).length();
                taskColWidths.put(task, Math.max(Math.max(nameLen, scoreLen), avgLen));
            }
            int totalColWidth = Math.max("Total".length(), String.format("%.2f", totalAverage).length());
            int markColWidth = Math.max("Mark".length(),
                    sortedResults.stream()
                            .map(cr -> calculateMark(
                                    (double) cr.getTaskResults().values().stream().mapToInt(Integer::intValue).sum() / cr.getTaskResults().size()))
                            .mapToInt(String::length)
                            .max()
                            .orElse("Mark".length()));

            // 5. 构建表头
            StringBuilder sb = new StringBuilder();
            // Student列
            sb.append(String.format("%-" + studentColWidth + "s", "Student"));
            // 任务列
            for (String task : sortedTaskNames) {
                sb.append("|").append(String.format("%-" + taskColWidths.get(task) + "s", task));
            }
            // Total和Mark列
            sb.append("|").append(String.format("%-" + totalColWidth + "s", "Total"))
                    .append("|").append(String.format("%-" + markColWidth + "s", "Mark"))
                    .append("|\n");

            // 6. 构建学生行
            for (CourseResult cr : sortedResults) {
                Person p = cr.getPerson();
                String studentName = p.getLastName() + " " + p.getFirstName();
                sb.append(String.format("%-" + studentColWidth + "s", studentName));

                for (String task : sortedTaskNames) {
                    int score = cr.getTaskResults().getOrDefault(task, 0);
                    sb.append("|").append(String.format("%-" + taskColWidths.get(task) + "d", score));
                }

                double avg = (double) cr.getTaskResults().values().stream().mapToInt(Integer::intValue).sum() / cr.getTaskResults().size();
                sb.append("|").append(String.format("%-" + totalColWidth + ".2f", avg))
                        .append("|").append(String.format("%-" + markColWidth + "s", calculateMark(avg)))
                        .append("|\n");
            }

            // 7. 构建平均值行
            sb.append(String.format("%-" + studentColWidth + "s", "Average"));
            for (String task : sortedTaskNames) {
                double avg = taskAverages.get(task);
                sb.append("|").append(String.format("%-" + taskColWidths.get(task) + ".2f", avg));
            }
            sb.append("|").append(String.format("%-" + totalColWidth + ".2f", totalAverage))
                    .append("|").append(String.format("%-" + markColWidth + "s", calculateMark(totalAverage)))
                    .append("|\n");

            return sb.toString();
        }
    );
}

private String calculateMark(double avg) {
    return avg > 90 ? "A" 
        : avg >= 83 ? "B" 
            : avg >= 75 ? "C" 
                : avg >= 68 ? "D" 
                    : avg >= 60 ? "E" 
                        : "F";
}

关键改动说明:

  • 全量数据收集:将原StringBuilder容器替换为ArrayList<CourseResult>,先收集所有结果,才能统一处理表头、排序和平均值。
  • 统一任务列:用TreeSet自动排序所有任务名称,保证表头任务列有序。
  • 自适应列宽计算:分别计算每列的最大宽度(表头文本、数据值、平均值字符串的长度最大值),确保列宽能容纳所有内容。
  • 完整表格生成:按表头→排序后的学生行→平均值行的顺序拼接,完全匹配示例表格结构。

内容的提问来源于stack exchange,提问作者Ksenia

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.29 13:59:56