Skip to content
章节导航

averagingDouble

Collectors.averagingDouble() 是一个用于计算数值类型元素平均值的收集器,专门用于 Double 类型的数据。

基本用法

java
public static void main(String[] args) {
        // 1. 基本用法 - 计算 Double 流的平均值
        List<Double> numbers = Arrays.asList(1.5, 2.5, 3.5, 4.5, 5.5);

        Double average = numbers.stream().collect(Collectors.averagingDouble(n -> n));
        System.out.println("Average: " + average); // 3.5

        // 2. 简化写法
        Double average2 = numbers.stream()
                .collect(Collectors.averagingDouble(Double::doubleValue));

        // 3. 从 IntStream/LongStream 转换
        double intAverage = IntStream.of(1, 2, 3, 4, 5)
                .boxed()
                .collect(Collectors.averagingDouble(Integer::doubleValue));
        System.out.println("Int average: " + intAverage); // 3.0
    }

核心特性

1. 处理空流和 null 值

java
public static void main(String[] args) {
        // 空流返回 0.0
        Double emptyAverage = Stream.<Double>empty()
                .collect(Collectors.averagingDouble(n -> n));
        System.out.println("Empty stream average: " + emptyAverage); // 0.0

        // 包含 null 值的流
        List<Double> withNulls = Arrays.asList(1.0, null, 3.0, null, 5.0);
        Double average = withNulls.stream()
                .filter(Objects::nonNull)  // 必须过滤 null
                .collect(Collectors.averagingDouble(n -> n));
        System.out.println("Average with nulls filtered: " + average); // 3.0

        // 如果不过滤 null 会抛出 NullPointerException
        // withNulls.stream().collect(Collectors.averagingDouble(n -> n));
    }

2. 精度处理

java
public static void main(String[] args) {
        List<Double> numbers = Arrays.asList(1.0, 2.0, 3.0);
        
        Double average = numbers.stream()
            .collect(Collectors.averagingDouble(n -> n));
        // 注意浮点数精度问题
        System.out.println("Average: " + average); // 2.0
        
        // 大数和小数混合的情况,可能会有精度损失
        List<Double> mixed = Arrays.asList(1e10, 1e-10, 1e10);
        Double mixedAverage = mixed.stream()
            .collect(Collectors.averagingDouble(n -> n));
        System.out.println("Mixed average: " + mixedAverage); 
    }

实际应用示例

示例 1:对象属性平均值计算

java
class Student {
    private String name;
    private double score;
    private int age;
    
    public Student(String name, double score, int age) {
        this.name = name;
        this.score = score;
        this.age = age;
    }
    
    // getters
    public double getScore() { return score; }
    public int getAge() { return age; }
    public String getName() { return name; }
}

public class ObjectPropertyAverage {
    public static void main(String[] args) {
        List<Student> students = Arrays.asList(
            new Student("Alice", 85.5, 20),
            new Student("Bob", 92.0, 21),
            new Student("Charlie", 78.5, 22),
            new Student("Diana", 88.0, 20),
            new Student("Eve", 95.5, 21)
        );
        
        // 计算平均分数
        Double averageScore = students.stream()
            .collect(Collectors.averagingDouble(Student::getScore));
        
        System.out.println("Average score: " + averageScore); // 87.9
        
        // 计算平均年龄(需要转换 int -> double)
        Double averageAge = students.stream()
            .collect(Collectors.averagingDouble(Student::getAge));
        
        System.out.println("Average age: " + averageAge); // 20.8
        
        // 使用 map 提取分数再计算
        Double averageScore2 = students.stream()
            .mapToDouble(Student::getScore)
            .average()
            .orElse(0.0);
        
        System.out.println("Average score (alternative): " + averageScore2);
    }
}

示例 2:与 groupingBy() 结合使用

java
public class GroupingWithAverage {
    public static void main(String[] args) {
        List<Student> students = Arrays.asList(
            new Student("Alice", "Math", 85.5),
            new Student("Bob", "Science", 92.0),
            new Student("Charlie", "Math", 78.5),
            new Student("Diana", "Science", 88.0),
            new Student("Eve", "Math", 95.5),
            new Student("Frank", "Science", 82.0)
        );
        
        // 按科目分组并计算每科平均分
        Map<String, Double> avgScoreBySubject = students.stream()
            .collect(Collectors.groupingBy(
                Student::getSubject,
                Collectors.averagingDouble(Student::getScore)
            ));
        
        System.out.println("Average score by subject:");
        avgScoreBySubject.forEach((subject, avg) -> 
            System.out.println(subject + ": " + avg));
        // Math: 86.5, Science: 87.333...
        
        // 更复杂的场景:按年级和科目双重分组
        // 假设 Student 有 getGrade() 方法
        Map<Integer, Map<String, Double>> nestedAvg = students.stream()
            .collect(Collectors.groupingBy(
                Student::getGrade,
                Collectors.groupingBy(
                    Student::getSubject,
                    Collectors.averagingDouble(Student::getScore)
                )
            ));
    }
}

示例 3:复杂数据聚合

java
class Order {
    private String customerId;
    private List<OrderItem> items;
    private LocalDate orderDate;
    
    public double getTotalAmount() {
        return items.stream()
            .mapToDouble(OrderItem::getPrice)
            .sum();
    }
    
    // getters
}

class OrderItem {
    private String productId;
    private double price;
    private int quantity;
    
    public double getPrice() { return price * quantity; }
}

public class BusinessDataAnalysis {
    public static void main(String[] args) {
        List<Order> orders = getOrders();
        
        // 计算每个客户的平均订单金额
        Map<String, Double> avgOrderByCustomer = orders.stream()
            .collect(Collectors.groupingBy(
                Order::getCustomerId,
                Collectors.averagingDouble(Order::getTotalAmount)
            ));
        
        // 计算每月的平均订单金额
        Map<YearMonth, Double> avgOrderByMonth = orders.stream()
            .collect(Collectors.groupingBy(
                order -> YearMonth.from(order.getOrderDate()),
                Collectors.averagingDouble(Order::getTotalAmount)
            ));
        
        // 计算每个产品的平均销售价格
        // 需要 flatMap 展开订单项
        Map<String, Double> avgPriceByProduct = orders.stream()
            .flatMap(order -> order.getItems().stream())
            .collect(Collectors.groupingBy(
                OrderItem::getProductId,
                Collectors.averagingDouble(OrderItem::getPrice)
            ));
    }
}

常见陷阱

陷阱 1:浮点数精度问题

java
public class FloatingPointPrecision {
    public static void main(String[] args) {
        // 浮点数计算可能有精度问题
        List<Double> numbers = Arrays.asList(0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1);
        
        Double average = numbers.stream()
            .collect(Collectors.averagingDouble(n -> n));
        
        System.out.println("Average of ten 0.1s: " + average);
        // 理论上应该是 0.1,但实际上可能是 0.09999999999999999
        
        // 解决方案:使用 BigDecimal 处理精度要求高的场景
        BigDecimal exactAverage = numbers.stream()
            .map(BigDecimal::valueOf)
            .reduce(BigDecimal.ZERO, BigDecimal::add)
            .divide(BigDecimal.valueOf(numbers.size()), 2, RoundingMode.HALF_UP);
        
        System.out.println("Exact average: " + exactAverage); // 0.10
    }
}

陷阱 2:空值和异常处理

java
public class NullHandlingBestPractice {
    static class Product {
        private Double price;  // 可能为 null
        
        public Product(Double price) {
            this.price = price;
        }
        
        public Double getPrice() {
            return price;
        }
    }
    
    public static void main(String[] args) {
        List<Product> products = Arrays.asList(
            new Product(100.0),
            new Product(null),     // 价格未知
            new Product(200.0),
            new Product(null)
        );
        
        // 最佳实践:明确处理 null 值
        Double avgPrice = products.stream()
            .filter(p -> p.getPrice() != null)
            .collect(Collectors.averagingDouble(Product::getPrice));
        
        System.out.println("Average price (excluding nulls): " + avgPrice); // 150.0
        
        // 或者给 null 一个默认值
        Double avgPriceWithDefault = products.stream()
            .collect(Collectors.averagingDouble(p -> 
                p.getPrice() != null ? p.getPrice() : 0.0));
        
        System.out.println("Average price (with default): " + avgPriceWithDefault); // 75.0
    }
}