Khi tham gia vào dự án thực tế thì bạn sẽ thấy rằng không xử lý code được với Java Streams - Java 8 thì source code của mình nhìn sẽ "quê" thế nào.
"Stream" được sử dụng trong bài viết này không có liên quan đến stream mà các bạn đã biết trong Java IO như InputStream
, OuputStream
.
Một Stream thể hiện cho một "sequence of data".
Một Stream pipeline là tập hợp các operation được thực hiện trên stream và tạo ra một kết quả. Ta có thể tưởng tượng Stream pipeline giống như những dây chuyền sản xuất trong nhà máy. Ví dụ với dây chuyền đóng gói sản phẩm ta cần làm những bước sau:
Với mỗi Stream pipeline sẽ bao gồm 3 phần:
Trong Java có 2 loại Stream:
# | Intermediate Operation | Terminal Operations |
---|---|---|
Thành phần bắt buộc của 1 Stream pipeline | NO | YES |
Xuất hiện nhiều lần trong 1 Stream pipeline | YES | NO |
Return type là 1 Stream | YES | NO |
Lazy evaluation | YES | NO |
Stream còn hợp lệ sau khi call | YES | NO |
Lazy evaluation: Intermediate operations sẽ chỉ thực hiện khi terminal operation được gọi.
Thông thường Stream có thể được tạo từ Collection:
1 2 3 4 5 6 | List<String> strList = Arrays.asList("A", "B"); Set<Integer> strSet = new HashSet<>(Arrays.asList(1, 2, 3)); Stream<String> stream = strList.stream(); Stream<String> stream01 = strList.parallelStream(); Stream<Integer> stream02 = strSet.stream(); |
Hoặc chúng ta có thể tạo Stream từ fixed value:
1 2 3 | Stream<String> stream = Stream.empty(); // count = 0 Stream<Integer> stream01 = Stream.of(1, 2); // count = 2 Stream<Double> stream02 = Stream.of(1.5, 2.6, 3.7); // count = 3 |
count()
methodMethod Signature:
long count()
Return về tổng số lượng phần tử trong Stream
1 2 3 | List<String> strList = Arrays.asList("A", "B"); long count = strList.stream().count(); System.out.println(count); // Print "2" |
min()/max()
methodMethod Signature:
Optional<T> min(Comparator<? super T> comparator)
Optional<T> max(Comparator<? super T> comparator)
Cho phép truyền vào một custom comparator để tìm giá trị lớn nhất và nhỏ nhất. Những method này return về Optional
có nghĩa rằng sẽ trường hợp ta không tìm được min/max, ví dụ: Stream source bị empty.
1 2 3 4 5 6 7 | List<Integer> strList = Arrays.asList(7, 9, 7); Optional<Integer> minOpt = strList.stream().min(Comparator.comparingInt(s -> s)); minOpt.ifPresent(System.out::println); // Print 7 Optional<Integer> maxOpt = strList.stream().max(Comparator.comparingInt(s -> s)); maxOpt.ifPresent(System.out::println); // Print 9 |
findAny()/findFirst()
methodMethod Signature:
Optional<T> findFirst();
Optional<T> findAny();
Method sẽ return 1 element trong stream ngoại trừ trường hợp stream bị empty. Method findFirst()
sẽ luôn trả về phần tử đầu tiên của stream. Còn method findAny()
sẽ hữu ích khi chúng ta dùng parallel stream, nó sẽ giúp bạn return về phần tử đầu tiên mà nó sẽ gặp trong bất kỳ luồng xử lý nào thay vì phần tử đầu tiên của stream. Vì thể mà kết quả mỗi lần trả về có thể khác nhau.
1 2 3 4 5 6 7 | Optional<String> findFirstResult = Stream.of("Winzone.vn", "Winzone blog") .findFirst(); Optional<String> findAnyResult = Arrays.asList("Winzone.vn", "Winzone blog").parallelStream() .findAny(); findFirstResult.ifPresent(System.out::println); // Always print "Winzone.vn" findAnyResult.ifPresent(System.out::println); //"Winzone.vn" or "Winzone blog" |
allMatch()/anyMatch()/noneMatch()
methodMethod Signature:
boolean allMatch(Predicate<? super T> predicate) boolean anyMatch(Predicate<? super T> predicate) boolean noneMatch(Predicate<? super T> predicate)
Những method này sẽ nhận đầu vào là 1 custom predicate và thực hiện search data của stream dựa trên predicate đó, giá trị trả về sẽ là true/false
tùy thuộc vào dữ liệu.
1 2 3 4 5 6 7 8 9 10 11 | List<String> strList = Arrays.asList("Winzone.vn", "FA", "Fresher Academy"); Predicate<String> predicate = str -> str.length() > 3; // Method sẽ return TRUE khi có 1 element match predicate System.out.println(strList.stream().anyMatch(predicate)); // true // Method sẽ return TRUE khi tất cả elements match predicate System.out.println(strList.stream().allMatch(predicate)); // false // Method sẽ return TRUE khi tất cả elements không match predicate System.out.println(strList.stream().noneMatch(predicate)); // false |
forEach()
methodMethod Signature:
void forEach(Consumer<? super T> action)
Method nhận vào 1 custom Consumer action, với cấu trúc loop tất cả các element của stream sẽ đều thực hiện action. Lưu ý method này terminal operation return void
, do đó muốn thay đổi dữ liệu thì cần xử lý trong custom Consumer truyền vào.
1 2 | Stream.of("Winzone", "Sharing knowledge") .forEach(s -> System.out.print(s + ", ")); // Winzone, Sharing knowledge, |
collect()
methodMethod Signature:
<R> R collect(Supplier<R> supplier, BiConsumer<R, ? super T> accumulator, BiConsumer<R, R> combiner);
<R, A> R collect(Collector<? super T, A, R> collector);
Đây là một trong những method hay sử dụng nhất, nó sẽ giúp bạn transform data của Stream source sang một dạng mới theo mong muốn (Thông thường là List, Set, Map).
Stream<String> stream1 = Stream.of("w", "i", "n"); Stream<String> stream2 = Stream.of( "z", "o", "n", "e"); Set<String> set = stream1.collect(Collectors.toCollection(LinkedHashSet::new)); List<String> list = stream2.collect(Collectors.toList()); System.out.println(set); // [w, i, n] System.out.println(list); // [z, o, n, e]
filter()
methodMethod Signature:
Stream<T> filter(Predicate<? super T> predicate);
Đây là method giúp ta loại bỏ dữ liệu không phù hợp, tham số đầu vào là 1 Predicate. Những item có giá trị không phù hợp với điều kiện của Predicate sẽ bị loại bỏ.
Stream<String> s = Stream.of("winzone.vn", "blog winzone.vn", "Java"); s.filter(x -> x.startsWith("winzone")).forEach(System.out::print); // winzone.vn
map()
methodMethod Signature:
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
Đây là method giúp ta convert item của stream source thành một loại dữ liệu mới để tiếp tục xử lý ở next stream hoặc terminate sang 1 dạng data type mới.
List<String> list = Arrays.asList("win", "zone"); List<String> newUppercaseList = list.stream() .map(s -> s.toUpperCase()).collect(Collectors.toList()); System.out.println(newUppercaseList); // [WIN, ZONE]
sorted()
methodMethod Signature:
Stream<T> sorted(); Stream<T> sorted(Comparator<? super T> comparator);
Đây là method giúp sắp xếp các phần tử trong stream source, mặc định sẽ theo thứ tự tăng dần nếu không được truyền vào lambda expression.
List<String> list = Arrays.asList("zone", "win", "blog"); List<String> newSortedList = list.stream().sorted().collect(Collectors.toList()); System.out.println(newSortedList); // [blog, win, zone]
List<String> list = Arrays.asList("zone", "win", "blog"); List<String> newSortedList = list.stream() .sorted(Comparator.reverseOrder()) // DESC .collect(Collectors.toList()); System.out.println(newSortedList); // [zone, win, blog]
Với Stream ta có thể kết hợp nhiều intermedia operator cùng 1 lúc để xử lý và terminal operator để ouput ra một kết quả theo mong muốn.
Giả sử có có 1 List<String>
như sau: ["win", "zone", "ab", null, "vn", "blog", "win"]
, và yêu cầu bài toán như sau:
null
Ta có thể xử lý ngắn gọn với Stream như sau:
List<String> list = Arrays.asList("win", "zone", "ab", null, "vn", "blog", "win"); Set<String> newList = list.stream() .filter(s -> Objects.nonNull(s) && s.length() > 2) .map(String::toUpperCase) .sorted(Comparator.reverseOrder()) .collect(Collectors.toCollection(LinkedHashSet::new)); System.out.println(newList); // [ZONE, WIN, BLOG]
Stream còn rất nhiều Intermediate và Terminal operation hữu ích khác chưa thể giới thiệu trong bài viết này. Tuy nhiên bài viết này cũng đã cung cấp đến cho các bạn những kiến thức cơ bản về cú pháp, cách dùng và cách thức hoạt động của Stream API.
Mong rằng nó sẽ là nền tảng để giúp các bạn tiếp cận với những method còn lại. Hãy thực hành thật nhiều để source code nhìn thật "xịn sò" nhé.