JDA中遍历排序Map时异步方法致排行榜顺序错误的解决问询
问题原因
你代码里的retrieveUserById(userId).queue()是异步操作——每发起一个用户ID查询请求后,程序不会等Discord API返回结果就直接处理下一个条目。不同用户的查询响应速度不一样,回调函数的执行顺序完全和你预先排序好的sortedEntries顺序无关,这就导致最终拼接的排行榜内容顺序乱了。再加上rank.getAndIncrement()是在异步回调里执行的,排名的递增顺序跟着回调走,自然和预期的分数排序不匹配。
解决办法
推荐用CompletableFuture收集所有用户查询的结果,等全部请求完成后,再按照原排序顺序组装排行榜内容:
case "leaderboard": EmbedBuilder embedBuilder = new EmbedBuilder(); embedBuilder.setTitle("Leaderboard"); embedBuilder.setColor(Color.GREEN); List<Map.Entry<Long, Long>> sortedEntries = new ArrayList<>(LeaderBoardData.loadYamlFile("leaderboard.yml").entrySet()); sortedEntries.sort(Map.Entry.comparingByValue(Comparator.reverseOrder())); // 只取前10条,减少不必要的API请求 List<Map.Entry<Long, Long>> top10 = sortedEntries.stream().limit(10).collect(Collectors.toList()); // 收集所有异步查询的结果Future List<CompletableFuture<String>> userNameFutures = new ArrayList<>(); for (Map.Entry<Long, Long> entry : top10) { long userId = entry.getKey(); // 提交异步查询,获取用户名的Future CompletableFuture<String> userNameFuture = TestBot.jda.retrieveUserById(userId) .submit() .thenApply(user -> user != null ? user.getEffectiveName() : "N/A") .exceptionally(error -> "N/A"); // 查询失败时返回默认值 userNameFutures.add(userNameFuture); } // 等所有查询完成后组装消息 CompletableFuture.allOf(userNameFutures.toArray(new CompletableFuture[0])).thenRun(() -> { StringBuilder leaderboardMessage = new StringBuilder(); for (int i = 0; i < top10.size(); i++) { String userName = userNameFutures.get(i).join(); long score = top10.get(i).getValue(); // 用循环索引直接生成排名,保证顺序正确 leaderboardMessage.append("**").append(i + 1).append("**. ") .append(userName) .append(": ") .append(score) .append("\n"); } embedBuilder.setDescription(leaderboardMessage.toString()); event.replyEmbeds(embedBuilder.build()).queue(); }); break;
关键改进点
- 提前截取前10条数据,避免发起过多不必要的Discord API请求
- 用
CompletableFuture保存每个条目的查询结果,后续组装时严格对应原排序顺序 - 直接用循环索引生成排名,彻底避免异步操作导致的排名混乱
- 添加异常处理,单个用户查询失败不会影响整个排行榜生成
内容的提问来源于stack exchange,提问作者Kasuki_ovo
相关产品推荐
相关产品推荐

