Java Stream API如何按平均分排序?代码实现疑问及优化指导请求
Hey there! Let's tackle that sorting problem you're running into with your Java Stream code. I see exactly where the issue is, and we can fix it while also making your code a bit more efficient.
The Problem with Your Current Sorting Logic
Your current approach of casting the difference between two averages to int ((int) (average2 - average1)) has a critical flaw: when the difference between two averages is less than 1 (like 5.0 vs 4.9), casting to int will truncate the value to 0. This tells the comparator that the two elements are equal, so their relative order becomes unpredictable. That's not what you want for a proper descending sort by average grade.
Solution 1: Use Double.compare() for Correct Comparisons
Instead of casting the difference, use Double.compare(double d1, double d2) which is designed exactly for this scenario. It returns the correct int value (-1, 0, or 1) based on the comparison of two doubles. For descending order, you'll want to compare the second element's average to the first:
sorted((a, b) -> { double average1 = a.getValue().stream().mapToDouble(x -> x).average().getAsDouble(); double average2 = b.getValue().stream().mapToDouble(x -> x).average().getAsDouble(); return Double.compare(average2, average1); })
Solution 2: Optimize by Calculating Averages Only Once
Even better, your current code calculates each student's average three times (once in filter, once in sorted, once in forEach) which is unnecessary overhead. Let's refactor the code to compute each average just once using map() to create a new entry with the precomputed average:
import java.util.*; import java.util.AbstractMap.SimpleEntry; public class Main { public static void main(String[] args) { Scanner scanner = new Scanner(System.in); int n = Integer.parseInt(scanner.nextLine()); Map<String, List<Double>> records = new HashMap<>(); while(n > 0){ String name = scanner.nextLine(); double grade = Double.parseDouble(scanner.nextLine()); records.putIfAbsent(name, new ArrayList<>()); records.get(name).add(grade); n--; } records.entrySet().stream() // First, compute each student's average once and store it with their name .map(entry -> { double average = entry.getValue().stream() .mapToDouble(Double::doubleValue) .average() .getAsDouble(); return new SimpleEntry<>(entry.getKey(), average); }) // Filter students with average >=4.50 .filter(entry -> entry.getValue() >= 4.50) // Sort descending by average using Double.compare .sorted((a, b) -> Double.compare(b.getValue(), a.getValue())) // Output the result .forEach(entry -> System.out.printf("%s -> %.2f%n", entry.getKey(), entry.getValue())); } }
Why This Works
Double.compare(average2, average1)handles all double comparison cases correctly, even when the difference is a fractional number. It returns:- A positive integer if
average2is greater thanaverage1 - 0 if they are equal
- A negative integer if
average2is less thanaverage1
- A positive integer if
- Precomputing the average in the
map()step reduces redundant calculations, making your code faster especially when dealing with large datasets.
Testing this with your sample input will give you the correct sorted output as expected:
John -> 5.00 George -> 5.00 Alice -> 4.50
内容的提问来源于stack exchange,提问作者ccc6




