Java Stream – Collectors và Statistics

Bài viết được sự cho phép của tác giả Kiên Nguyễn

Làm việc với Stream Collector/ Collectors đã lâu, liệu rằng bạn có biết ngoài Grouping và Partitioning, Stream còn hỗ trợ cả thống kê (Statistics). Bất ngờ chưa, Collector thật sự còn ẩn chứa nhiều điều mà anh em còn chưa biết tới.

Hãy cùng tìm hiểu collector và các methods của nó qua bài viết dưới đây. Cuộc đời anh em developer chúng ta sẽ bớt khổ.

Xem thêm các Việc làm Java hấp dẫn trên TopDev

  10 Java Web Framework tốt nhất
  10 lý do cho thấy tại sao bạn nên theo học ngôn ngữ lập trình Java

Trường hợp muốn tìm hiểu sâu hơn về stream và stream how it’s works, các bạn có thể khảo bài viết này.

Thực sự những method được cung cấp bởi collectors như Partitioning hay Grouping có sức hấp dẫn lạ kì.

1. Collectors.

A Collector represents a way to combine the elements of a Stream into one result.

Collector đại diện cho cách kết hợp tất cả các đối tượng trong Stream thành một kết quả duy nhất.

Để hoàn thành nhiệm vụ của mình, collectors sẽ chịu trách nhiệm thực thi 3 nhóm tác vụ chính:

  • A supplier of an initial value. Cung cấp một giá trị ban đầu.
  • An accumulator which adds to the initial value. – Nơi tổng hợp một loạt các giá trị ban đầu.
  • A combiner which combines two results into one. – Kết hợp hai kết quả thành một.

Collector được sử dụng thường xuyên khi ta sử dụng Stream API, nhưng ta ít khi để ý tới. Có hai cách để sử dụng:

// Phương thức ngắn gọn.
collect(Collector) (types left off for brevity)
// Các phương thức chi tiết để có một collection.
collect(supplier,accumulator,combiner).
Hình ảnh mô tả các bước để có một Collection thông qua Collector. Giá trị trả về sẽ là Optional.

Phương thức import để sử dụng collectors là:

import staticjava.util.stream.Collectors.*;

1.1 Collectors đơn giản.

Hai methods đơn giản và thường được sử dụng nhiều với collectors là toList() và toCollection().

// Lấy danh sách tên toàn bộ gái xinh, chuyển qua List.
List<String> listGaiXinh = Gai.stream() .map(Gai::getName)
.collect(toList());

// Lấy danh sách tên toàn bộ gái xinh, chuyển qua TreeSet.
Set<String> setGaiXinh = Gai.stream() .map(Gai::getName)
.collect(toCollection(TreeSet::new));

1.2 Joinning.

Trước đây, muốn Joining chuỗi trong Java, thường ta sử dụng Apache Common StringUtils.join. Khi sử dụng collector, ta có thể gọi phương thức joining. Khi gọi phương thức này, bản thân collectors lúc này biến thành một thùng chứa, kết nối các chuỗi String nhỏ, cho ra một String lớn.

// Tạo chuỗi gái xinh, cách nhau bởi dấu phẩy.
gaiXinhStr = gai.stream() .map(Gai::getName)
.collect(joining(","));
stream-collector-ho-tro-joining-chuoi-string-cuc-totXưa rồi nhé StringUtils của Apache. Stream nay hỗ trợ joining chỉ trong một nốt nhạc.

2. Statistics.

Ngoài công việc kết nối hoặc tổng hợp một loạt các giá trị. Collector còn cung thêm cả chức năng thống kê (statistics).

// Đọc file Rio, tính trung bình các dòng khác rỗng.
System.out.println(
Files.lines(Paths.get("Rio.java"))
.map(String::trim)
.filter(s -> !s.isEmpty())
.collect(averagingInt(String::length))
);

2.1 Summarizing.

Summarizing là một method của collector, phương thức này trả về một lớp đặc biệt chứa thông tin thống kê (statistical information). Thông tin này thống kê dữ liệu cuối đã được trích xuất khỏi Stream.

// Lấy danh sách gái xinh có số đo vòng một cực lớn.
// Ôi, tao thích Collectos quá đi thôi. =))) 
int soEmVongMotLon = gaiXinh.stream()
.collect(summarizingInt(Gai::vongMotLon));

2.2 Averaging.

Phương thức averaging trả về giá trị trung bình của các đối tượng.

// Tính tuổi trung bình danh sách gái xinh.
// Có collectors, đã không còn cực như xưa.
int tuoiTrungBinh = gaiXinh.stream()
.collect(averagingInt(Gai::getAge));

2.3 Summing.

Tương tự như tính giá trị trung bình, Summing trả về tổng các đối tượng trong Stream.

// Tính tổng số tuổi danh sách gái xinh.
// Có collectors, đã không còn cực như xưa.
int tongSoTuoi = gaiXinh.stream()
.collect(SummingInt(Gai::getAge));
Stream-collector-summing-co-che-congLuồng thực hiện summing ở Stream. Các phép cộng được thực hiện tuần tự ở mỗi lần gọi method getCalories.

2.4 MaxBy / MinBy.

MaxBy/MinBy collectors return the biggest/the smallest element of a Stream according to a provided Comparator instance.

MaxBy/MinBy collectors trả về giá trị lớn nhất/ nhỏ nhất của Stream theo thể hiện được cung cấp bởi Comparator.

// Lấy 
// Có collectors, đã không còn cực như xưa.
Optional<String> result = gaiXinh.stream()
.collect(maxBy(Comparator.getAge()));

Có một lưu ý nhỏ là giá trị trả về khi gọi phương thức MaxBy và MinBy phải được đóng gói theo kiểu Optional instance.

2.5 Grouping By.

GroupingBy collector is used for grouping objects by some property and storing results in a Map instance.

GroupingBy collector được sử dụng để gom một số đối tượng bởi thuộc tính của chúng, kết quả trả về sẽ được lưu vào một thể hiện (instance) của Map.

Map<Integer, Set<String>> result = givenList.stream()
.collect(groupingBy(String::length, toSet()));
Java-Stream-Collectors-Grouping-ByCơ chế groupingBy ở Stream tương tự như GROUP_BY ở SQL, các nhóm sẽ gom lại với nhau tùy theo điều kiện được thiết lập ở Stream.

2.6 Partitioning By.

PartitioningBy is a specialized case of groupingBy that accepts a Predicate instance and collects Stream elements into a Map instance that stores Boolean values as keys and collections as values.

PartitioningBy là một trường hợp đặc biệt của groupingBy, nó chấp nhận thêm Predicate Instance và Stream elements bên trong Map, các giá trị này sẽ được lưu kiểu Boolean giống như một keys.

Lý thuyết như thế này thì hơi khó hiểu. Ví dụ như sau: Nếu bạn có một nhóm bạn năm người. Trong đó, ba người ăn chayhai người còn lại thì không. Ta sẽ sử dụng partitioning để chia nhóm người thành 2 vùng (ăn chay và không).

Hai nhóm người được phân như sau:

  • Ăn chay: Hạnh, Hải, Anh, Ẩn.
  • Ăn mặn: Hậu, Vinh, Tú, Tùng.
Map<Boolean, List<Dish>> partitionedMenu = menu.stream()
.collect(partitioningBy(Dish::isVetetarian));

Kết quả trả về sẽ là:

  • True: [Hạnh, Hải, Anh, Ẩn].
  • False: [Hậu, Vinh, Tú, Tùng].

Chỉ nên sử dụng partitioningBy trong trường hợp phép so sánh để phân các đối tượng thành vùng là phức tạp. Ngược lại, đơn giản ta chỉ cần sử dụng Stream.filter().

List<Dish> vegetarianDishes = menu.stream()
.filter(Dish::isVegetarian)
.collect(toList());

3. Kết luận.

Stream và Collectors kết hợp lại với nhau thật sự là một cặp đôi song sát. Hữu hiệu và mạnh mẽ. Việc ghi nhớ các method được cung cấp bởi Collector sẽ rất hữu hiệu cho anh em trong quá trình code. Sử dụng linh hoạt collector giúp code trở nên đơn giảndễ đọc, dễ mantainance.

Ngoài ra, collectors còn trở nên mạnh mẽ hơn khi ta có thể custom một class của nó, tạo riêng cho mình một collector.

Khi cần thiết, hãy sử dụng Java Stream Collectors, cuộc đời bạn sẽ bớt khổ.Khi cần thiết, hãy sử dụng Java Stream Collectors, cuộc đời bạn sẽ bớt khổ.

Mong rằng bài viết này sẽ giúp đỡ các bạn phần nào.

Thanks and love u so much!.

4. Tham khảo.

Bài viết gốc được đăng tải tại kieblog.vn

Có thể bạn quan tâm:

Xem thêm Việc làm IT hấp dẫn trên TopDev