Giới thiệu và cách sử dụng các operations hữu ích thường được sử dụng với Stream<T>
trong Java.
Để dễ hiểu, Stream
đơn thuần dùng để xử lý các vấn đề thường gặp ở danh sách như.
- Duyệt danh sách.
- Lọc bớt phần tử trong danh sách theo các tiêu chí.
- Thêm hoặc cắt gọt các thuộc tính của các phần tử trong danh sách.
- Sắp xếp danh sách.
- ...
*Có thể nói, C# có LinQ thì Java có Stream.
Pipeline
Khi sử dụng Stream, bạn sẽ biết thêm về khái niệm Pipeline; có thể hiểu là 1 đường ống mà dòng chảy sẽ đi qua, trên đường ống này, có các operation (trong lập trình đơn thuần là các phương thức) sẽ làm thay đổi dòng chảy đó.
Operation có thể hiểu là các "đốt" mà ở đó dòng chảy sẽ thay đổi tính chất, có 2 loại operation là intermediate (đốt trên đường đi) và terminal (đốt cuối cùng).
Ví dụ có 1 danh sách tài khoản vừa truy vấn từ cơ sở dữ liệu và cần các thao tác sau trước khi trả về cho Frontend.
- Cắt gọt các thuộc tính
password
, hoặc các thông tin bảo mật trong tất cả tài khoản - dùngmap
operation. - Tiếp tục lọc bớt tất cả tài khoản đã tạo trước năm 1990 - dùng
filter
operation. - Tiếp tục thêm 1 thuộc tính là
fullName
vào tất cả tài khoản bằng cách nốifirstName
vàlastName
trong tài khoản - dùngmap
operation.

Phân loại operation
Có 2 loại operation:
- Intermediate operations: là các operations sau khi vận hành sẽ trả về 1 stream khác (lúc này dòng chảy vẫn chưa kết thúc).
- Terminal operations: là các operations sau khi vận hành sẽ không trả về 1 stream mà có thể là mảng, collections, giá trị hoặc không trả về kết quả (void) (dòng chảy kết thúc tại đây để đưa ra kết quả cuối cùng có thể dùng được).
Các intermediate operations thường được đặt trước terminal operation và chúng sẽ không được thực thi cho đến khi có terminal operation.
Các Intermediate operations
filter
Phương thức dùng để lọc ra tất cả những phần tử trong stream có giá trị thỏa với điều kiện.
Collection<String> collection = Arrays.asList("aa" , "ba", "aa", "abb"); Stream<String> streamOfCollection = collection.stream(); streamOfCollection.filter(x -> x.startsWith("a")) .forEach(x -> System.out.println(x + "; ")); //aa; aa; abb;
map
Map giá trị của từng phần tử trong Stream vào một function mà chúng ta truyền vào và trả về giá trị mới.
Stream<String> streamOfArray = Stream.of("aa", "aa", "abb"); streamOfArray.map(x -> x + "c") .forEach(x -> System.out.print(x + "; ")); //aac; bac; aac; abbc;
flatmap
Biến đổi mỗi phần tử trong Stream (thường là những phần tử phức tạp như List, Set, Array) thành những phần tử đơn giản hơn.
List<Integer> s1 = Arrays.asList(1, 2, 3, 4, 5); List<Integer> s2 = Arrays.asList(6, 7, 8, 9, 10); List<List<Integer>> s = Arrays.asList(s1, s2); Stream<List<Integer>> stream = s.stream(); stream.flatMap(a -> a.stream()) .forEach(x -> System.out.print(x + "; ")); //1; 2; 3; 4; 5; 6; 7; 8; 9; 10;
distinct
Loại bỏ các phần tử trùng nhau trong Stream và trả về 1 Stream với các phần tử riêng biệt.
Stream<String> streamOfArray = Stream.of("aa", "ba", "aa", "abb"); streamOfArray.distinct() .forEach(x -> System.out.print(x + "; ")); //aa; ba; abb;
sorted
Trả về một Stream mà các phần tử đã được sắp xếp theo thứ tự.
Stream<String> streamOfArray = Stream.of("aa", "ba", "aa", "abb"); streamOfArray.sorted((a,b)->a.compareTo(b)) .forEach(x -> System.out.print(x + "; ")); //aa; aa; abb; ba;
limit
Giới hạn số lượng phần tử tối đa trong Stream.
Stream<String> streamOfArray = Stream.of("aa", "ba", "aa", "abb"); streamOfArray.limit(3) .forEach(x -> System.out.print(x + "; ")); //aa; ba; aa;
peek
Gần giống như forEach, peek cho phép duyệt qua tất cả các phần tử trong Stream và thực hiện các tính toán trên các phần tử đó đồng thời trả về 1 Stream.
Stream<Integer> streamOfArray = Stream.of(1, 2, 3, 4); System.out.print("The number divisible by two is: "); long count = streamOfArray.peek(x -> {if (x % 2 == 0) System.out.print(x + "; ");}) .count(); //The number divisible by two is: 2; 4;
Các Terminal operations
forEach
Phương thức cho phép duyệt qua tất cả các phần từ trong Stream và thực hiện các tính toán từ các giá trị đó.
streamOfCollection.forEach(x -> System.out.println(x));
collect
Phương thức dùng để convert một Stream thành một List, Set, Map.
List<String> _stream = items.stream() .filter( item -> item.startsWith("stdio") ) .collect(Collectors.toList());
toArray()
Convert 1 Stream sang Array.
Stream<Integer> intStream = Stream.of(1,2,3,4); Integer[] intArray = intStream.toArray(Integer[]::new); System.out.println(Arrays.toString(intArray)); //[1, 2, 3, 4]
count
Trả về số lượng phần tử của stream sau khi thực hiện filter.
Stream<Integer> streamOfArray = Stream.of(1, 2, 3, 4); long count = streamOfArray.count(); System.out.println("Number of elements in the array is: " + count); Number of elements in the array is: 4
min
Trả về giá trị nhỏ nhất của Stream.
Stream.of(1, 5, 4) .min() .ifPresent(System.out::println); //1
max
Trả về giá trị lớn nhất của Stream.
Stream.of(1, 5, 4) .max() .ifPresent(System.out::println); //5
reduce
Giúp làm giảm các phần tử của stream về một giá trị.
Stream<Integer> streamOfArray = Stream.of(1, 2 , 3, 4); Integer result = streamOfArray.reduce((a, b) -> a + b) .get(); System.out.print(result); //10
Vì sao sử dụng Stream?
Stream cũng chỉ là 1 cách tiếp cận, 1 phương pháp code trong lập trình với Java và các danh sách. Tuy nhiên, nó tiện lợi, như những code mẫu về các operations trên, bạn không cần thiết phải for (int i = 0; i < accounts.length(); i++)
mọi lúc mọi nơi và thực tế của các trường hợp ứng dụng, vòng lặp for
biến i
hay accounts.length()
cũng không đóng góp vai trò gì ngoài việc kiểm soát chỉ số, số lần lặp nhưng lại chiếm dụng thời gian code, sự quan tâm và làm cho code dài hơn.