CodeGym /Các khóa học /JAVA 25 SELF /Các phương thức sum, count, average, max, min trong Strea...

Các phương thức sum, count, average, max, min trong Stream API

JAVA 25 SELF
Mức độ , Bài học
Có sẵn

1. Đếm phần tử: count()

Khi làm việc với các tập hợp số hoặc đối tượng, gần như luôn có nhu cầu tính toán điều gì đó: số lượng phần tử, tổng, trung bình, giá trị lớn nhất hoặc nhỏ nhất. Ví dụ: số nhân viên trong công ty, tổng lương, độ tuổi trung bình của sinh viên, sản phẩm đắt nhất, đơn hàng lớn nhất. Với Stream API, mọi thứ trở nên đơn giản và biểu đạt hơn.

Cách hoạt động

Phương thức count() là toán tử kết thúc của Stream API, trả về số lượng phần tử trong stream.

long count = employees.stream().count();

Nếu chỉ cần đếm những phần tử thỏa điều kiện — hãy dùng bộ lọc filter(...):

long richCount = employees.stream()
    .filter(e -> e.getSalary() > 100_000)
    .count();

Ví dụ cho ứng dụng của chúng ta:

Giả sử có danh sách sản phẩm:

List<Product> products = List.of(
    new Product("Sữa", 80),
    new Product("Phô mai", 250),
    new Product("Bánh mì", 40)
);

Hãy đếm bao nhiêu sản phẩm đắt hơn 100:

long expensiveCount = products.stream()
    .filter(p -> p.getPrice() > 100)
    .count();

System.out.println("Số sản phẩm đắt: " + expensiveCount);

Optional là gì

Trong Java có một container đặc biệt — Optional. Đây là một đối tượng bọc, có thể chứa giá trị hoặc rỗng. Nó được dùng khi kết quả có thể không tồn tại và cần biểu thị điều đó một cách tường minh. Ví dụ, các phương thức min(), max()average() có thể trả về kết quả rỗng nếu tập hợp rỗng. Thay vì null, sẽ trả về Optional.

Các phương thức chính của Optional

  • isPresent() / isEmpty() — kiểm tra có giá trị hay không.
  • orElse(giá trị) — trả về giá trị hoặc mặc định nếu rỗng.
  • orElseThrow() — ném ngoại lệ nếu rỗng.
  • ifPresent(...) — chỉ thực hiện hành động khi có giá trị.

Ví dụ:

OptionalInt min = products.stream()
    .mapToInt(Product::getPrice)
    .min();

if (min.isPresent()) {
    System.out.println("Giá tối thiểu: " + min.getAsInt());
} else {
    System.out.println("Danh sách rỗng");
}

Hoặc ngắn gọn hơn:

int minValue = min.orElse(-1);
System.out.println("Giá tối thiểu: " + minValue);

Cách tiếp cận này giúp tránh NullPointerException và làm cho mã an toàn hơn.

2. Tổng, trung bình, nhỏ nhất và lớn nhất cho dữ liệu số

Stream kiểu nguyên thủy: IntStream, DoubleStream, LongStream

Đối với dữ liệu số, Stream API cung cấp các stream chuyên biệt: IntStream, DoubleStream, LongStream. Chúng cho phép tính tổng, trung bình, nhỏ nhất và lớn nhất một cách dễ dàng và nhanh chóng.

Chuyển sang stream số

Để lấy stream số từ stream đối tượng, dùng các phương thức mapToInt, mapToDouble, mapToLong.

int sum = products.stream()
    .mapToInt(Product::getPrice)
    .sum();
System.out.println("Tổng giá: " + sum);

Các phương thức

  • sum() — tổng của tất cả phần tử.
  • average() — giá trị trung bình (trả về OptionalDouble).
  • min(), max() — giá trị nhỏ nhất và lớn nhất (trả về OptionalInt/OptionalDouble/OptionalLong).

Ví dụ:

// Tổng tất cả giá
int total = products.stream()
    .mapToInt(Product::getPrice)
    .sum();

// Giá trung bình
OptionalDouble avg = products.stream()
    .mapToInt(Product::getPrice)
    .average();

// Giá nhỏ nhất và lớn nhất
OptionalInt min = products.stream()
    .mapToInt(Product::getPrice)
    .min();

OptionalInt max = products.stream()
    .mapToInt(Product::getPrice)
    .max();

Cách lấy giá trị từ Optional

double average = avg.orElse(0.0); // 0.0 nếu tập hợp rỗng
int minValue = min.orElse(-1);    // -1 nếu tập hợp rỗng
int maxValue = max.orElse(-1);    // -1 nếu tập hợp rỗng

Câu đùa của lập trình viên: danh sách rỗng không phải là lỗi, chỉ là tình huống bạn không có dữ liệu. Nhưng nếu bạn cố lấy giá trị từ Optional rỗng bằng phương thức getAsInt() — bạn sẽ gặp NoSuchElementException. Hãy luôn dùng orElse hoặc isPresent()!

3. Tổng hợp đối tượng: Collectors.summingInt, averagingInt, maxBy, minBy

Nếu bạn có stream đối tượng và muốn tổng hợp theo một thuộc tính nào đó, hãy dùng các collector chuyên dụng từ lớp Collectors.

summingInt, averagingInt

int totalSalary = employees.stream()
    .collect(Collectors.summingInt(Employee::getSalary));

double avgSalary = employees.stream()
    .collect(Collectors.averagingInt(Employee::getSalary));

minBy, maxBy

Để tìm đối tượng có giá trị thuộc tính lớn nhất/nhỏ nhất:

Optional<Employee> richest = employees.stream()
    .collect(Collectors.maxBy(Comparator.comparingInt(Employee::getSalary)));

Optional<Employee> poorest = employees.stream()
    .collect(Collectors.minBy(Comparator.comparingInt(Employee::getSalary)));

Quan trọng: kết quả là Optional, vì tập hợp có thể rỗng.

Ví dụ

Tạo lớp Product:

public class Product {
    private final String name;
    private final int price;
    public Product(String name, int price) {
        this.name = name;
        this.price = price;
    }
    public String getName() { return name; }
    public int getPrice() { return price; }
}

Tạo danh sách sản phẩm:

List<Product> products = List.of(
    new Product("Sữa", 80),
    new Product("Phô mai", 250),
    new Product("Bánh mì", 40)
);

Tìm sản phẩm đắt nhất:

Optional<Product> maxProduct = products.stream()
    .collect(Collectors.maxBy(Comparator.comparingInt(Product::getPrice)));

maxProduct.ifPresent(p -> System.out.println("Sản phẩm đắt nhất: " + p.getName()));

4. Kết hợp với nhóm (grouping)

Các phương thức tổng hợp thường được kết hợp với nhóm. Ví dụ, có thể tìm giá trung bình của sản phẩm theo chữ cái đầu tiên của tên:

Map<Character, Double> avgPriceByLetter = products.stream()
    .collect(Collectors.groupingBy(
        p -> p.getName().charAt(0),
        Collectors.averagingInt(Product::getPrice)
    ));

System.out.println(avgPriceByLetter);

Kết quả:

{S=80.0, P=250.0, B=40.0}

5. Ví dụ thực tế

Ví dụ 1: Sinh viên và điểm

Danh sách sinh viên và điểm của họ:

public class Student {
    private final String name;
    private final int score;
    public Student(String name, int score) {
        this.name = name;
        this.score = score;
    }
    public String getName() { 
        return name; 
    }
    public int getScore() { 
        return score; 
    }
}

List<Student> students = List.of(
    new Student("Alisa", 90),
    new Student("Bob", 75),
    new Student("Vasya", 100)
);

Có bao nhiêu sinh viên có điểm cao hơn 80?

long count = students.stream()
    .filter(s -> s.getScore() > 80)
    .count();
System.out.println("Số sinh viên có điểm > 80: " + count);

Điểm trung bình:

double avg = students.stream()
    .mapToInt(Student::getScore)
    .average()
    .orElse(0.0);
System.out.println("Điểm trung bình: " + avg);

Sinh viên xuất sắc nhất:

students.stream()
    .max(Comparator.comparingInt(Student::getScore))
    .ifPresent(s -> System.out.println("Sinh viên tốt nhất: " + s.getName()));

Ví dụ 2: Số lượng sản phẩm duy nhất

long uniqueCount = products.stream()
    .map(Product::getName)
    .distinct()
    .count();
System.out.println("Số sản phẩm duy nhất: " + uniqueCount);

6. Những điểm hữu ích

Sơ đồ trực quan: các phương thức tổng hợp hoạt động thế nào

Bộ sưu tập đối tượng
   │
   ▼
stream()
   │
   ▼
mapToInt/Double/Long (nếu cần)
   │
   ▼
sum() / average() / min() / max() / count()
   │
   ▼
Kết quả (int, double, long, Optional)

Xử lý Optional: cách trích xuất giá trị an toàn

Vì sao các phương thức trả về Optional?
Nếu tập hợp rỗng (ví dụ bạn tìm giá trị lớn nhất trong danh sách rỗng), sẽ trả về Optional rỗng. Nếu gọi ngay getAsInt()/get(), ngoại lệ sẽ được ném ra.

Cách đúng:

  • Dùng orElse(giá trị mặc định)
  • Dùng ifPresent(...) để chỉ hành động khi có giá trị
  • Dùng orElseThrow(...) để ném ngoại lệ rõ ràng

Ví dụ:

OptionalDouble avg = products.stream()
    .mapToInt(Product::getPrice)
    .average();

System.out.println("Giá trung bình: " + avg.orElse(0.0));

Khi nào dùng stream nguyên thủy và khi nào — Collectors

  • Nếu chỉ cần tính tổng/trung bình/nhỏ nhất/lớn nhất theo một trường số — dùng mapToInt và phương thức tương ứng (sum, average, min, max).
  • Nếu muốn tổng hợp đối tượng (ví dụ tìm đối tượng có trường lớn nhất), dùng các collector maxBy/minBy.
  • Nếu cần nhóm và tính toán theo từng nhóm — sử dụng kết hợp groupingBy với các collector tổng hợp.

7. Các lỗi thường gặp khi làm việc với các phương thức tổng hợp

Lỗi #1: Không xử lý Optional. Người mới thường quên rằng min, max, average trả về Optional. Kết quả là khi cố lấy giá trị từ container rỗng sẽ xảy ra NoSuchElementException. Luôn dùng orElse, orElseThrow hoặc ifPresent.

Lỗi #2: Dùng stream thông thường thay vì mapToInt/mapToDouble. Nếu bạn viết stream().sum(), trình biên dịch sẽ nói: “Không có phương thức như vậy!”. Để tính tổng, trung bình, nhỏ nhất và lớn nhất cần các stream nguyên thủy.

Lỗi #3: Không khớp kiểu khi so sánh. Khi tìm lớn nhất/nhỏ nhất trong số các đối tượng, hãy dùng comparator đúng: Comparator.comparingInt(Employee::getSalary). Nếu không, có thể nhận lỗi biên dịch hoặc kết quả không chính xác.

Lỗi #4: So sánh đối tượng với null. Nếu trong tập hợp có phần tử null, việc tổng hợp có thể ném NullPointerException. Tốt hơn nên lọc trước các phần tử như vậy hoặc dùng Comparator.nullsLast(...)/nullsFirst(...).

Lỗi #5: Dùng sum/average/min/max với tập hợp rỗng mà không xử lý kết quả. Nếu tập hợp rỗng, phương thức sum() sẽ trả về 0, còn average()/min()/max() — trả về Optional rỗng. Đừng quên xử lý trường hợp này.

1
Nhiệm vụ
JAVA 25 SELF, mức độ, bài học
Đã khóa
Phân tích giá trong cửa hàng trực tuyến của bạn 🛍️
Phân tích giá trong cửa hàng trực tuyến của bạn 🛍️
1
Nhiệm vụ
JAVA 25 SELF, mức độ, bài học
Đã khóa
Báo cáo kết quả học tập sinh viên tại Học viện Phép thuật 🎓
Báo cáo kết quả học tập sinh viên tại Học viện Phép thuật 🎓
Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION