如何在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
相关产品推荐
相关产品推荐

