JavaRush /Blog Java /Random-VI /Nghỉ giải lao #177. Hướng dẫn chi tiết về Java Stream tro...

Nghỉ giải lao #177. Hướng dẫn chi tiết về Java Stream trong Java 8

Xuất bản trong nhóm
Nguồn: Hackernoon Bài đăng này cung cấp hướng dẫn chi tiết về cách làm việc với Java Stream cùng với các ví dụ và giải thích về mã. Nghỉ giải lao #177.  Hướng dẫn chi tiết về Java Stream trong Java 8 - 1

Giới thiệu về các luồng Java trong Java 8

Luồng Java, được giới thiệu như một phần của Java 8, được sử dụng để làm việc với các bộ sưu tập dữ liệu. Bản thân chúng không phải là một cấu trúc dữ liệu nhưng có thể được sử dụng để nhập thông tin từ các cấu trúc dữ liệu khác bằng cách sắp xếp thứ tự và tạo đường dẫn để tạo ra kết quả cuối cùng. Lưu ý: Điều quan trọng là không nhầm lẫn giữa Luồng và Chủ đề, vì trong tiếng Nga cả hai thuật ngữ này thường được gọi trong cùng một bản dịch là “dòng chảy”. Luồng biểu thị một đối tượng để thực hiện các thao tác (thường xuyên nhất là truyền hoặc lưu trữ dữ liệu), trong khi Thread (dịch theo nghĩa đen - thread) biểu thị một đối tượng cho phép mã chương trình nhất định được thực thi song song với các nhánh mã khác. Vì Stream không phải là cấu trúc dữ liệu riêng biệt nên nó không bao giờ thay đổi nguồn dữ liệu. Các luồng Java có các tính năng sau:
  1. Java Stream có thể được sử dụng bằng cách sử dụng gói “java.util.stream”. Nó có thể được nhập vào tập lệnh bằng mã:

    import java.util.stream.* ;

    Sử dụng mã này, chúng ta cũng có thể dễ dàng triển khai một số hàm dựng sẵn trong Java Stream.

  2. Luồng Java có thể chấp nhận đầu vào từ các bộ sưu tập dữ liệu như bộ sưu tập và mảng trong Java.

  3. Luồng Java không yêu cầu thay đổi cấu trúc dữ liệu đầu vào.

  4. Luồng Java không thay đổi nguồn. Thay vào đó, nó tạo ra đầu ra bằng các phương pháp đường ống thích hợp.

  5. Luồng Java phụ thuộc vào các hoạt động trung gian và đầu cuối mà chúng ta sẽ thảo luận trong các phần sau.

  6. Trong Luồng Java, các hoạt động trung gian được sắp xếp theo đường dẫn và diễn ra ở định dạng đánh giá từng phần. Chúng kết thúc bằng các chức năng đầu cuối. Điều này tạo thành định dạng cơ bản để sử dụng Java Stream.

Trong phần tiếp theo, chúng ta sẽ xem xét các phương thức khác nhau được sử dụng trong Java 8 để tạo Luồng Java khi được yêu cầu.

Tạo luồng Java trong Java 8

Các luồng Java có thể được tạo theo nhiều cách:

1. Tạo một luồng trống bằng phương thức Stream.empty()

Bạn có thể tạo một luồng trống để sử dụng sau này trong mã của mình. Nếu bạn sử dụng phương thức Stream.empty() , một luồng trống sẽ được tạo, không chứa giá trị nào. Luồng trống này có thể hữu ích nếu chúng ta muốn bỏ qua ngoại lệ con trỏ null khi chạy. Để làm điều này bạn có thể sử dụng lệnh sau:
Stream<String> str = Stream.empty();
Câu lệnh trên sẽ tạo ra một luồng trống có tên str mà không có bất kỳ phần tử nào bên trong nó. Để xác minh điều này, chỉ cần kiểm tra số lượng hoặc kích thước của luồng bằng thuật ngữ str.count() . Ví dụ,
System.out.println(str.count());
Kết quả là chúng ta nhận được 0 ở đầu ra .

2. Tạo luồng bằng phương thức Stream.builder() với phiên bản Stream.Builder

Chúng tôi cũng có thể sử dụng Trình tạo luồng để tạo luồng bằng mẫu thiết kế của trình tạo. Nó được thiết kế để xây dựng các đối tượng theo từng bước. Hãy xem cách chúng ta có thể khởi tạo một luồng bằng cách sử dụng Stream Builder .
Stream.Builder<Integer> numBuilder = Stream.builder();

numBuilder.add(1).add(2).add( 3);

Stream<Integer> numStream = numBuilder.build();
Sử dụng mã này, bạn có thể tạo luồng có tên numStream chứa các phần tử int . Mọi thứ được thực hiện khá nhanh chóng nhờ vào instance Stream.Builder có tên numBuilder được tạo trước tiên.

3. Tạo luồng với các giá trị được chỉ định bằng phương thức Stream.of()

Một cách khác để tạo luồng liên quan đến việc sử dụng phương thức Stream.of() . Đây là cách đơn giản để tạo luồng với các giá trị nhất định. Nó khai báo và khởi tạo thread. Một ví dụ về cách sử dụng phương thức Stream.of() để tạo luồng:
Stream<Integer> numStream = Stream.of(1, 2, 3);
Mã này sẽ tạo một luồng chứa các phần tử int , giống như chúng ta đã làm trong phương thức trước đó bằng cách sử dụng Stream.Builder . Ở đây chúng ta đã trực tiếp tạo một luồng bằng cách sử dụng Stream.of() với các giá trị được xác định trước [1, 2 và 3] .

4. Tạo luồng từ một mảng hiện có bằng phương thức Arrays.stream()

Một phương pháp phổ biến khác để tạo một luồng liên quan đến việc sử dụng mảng trong Java. Luồng ở đây được tạo từ một mảng hiện có bằng phương thức Arrays.stream() . Tất cả các phần tử mảng được chuyển đổi thành phần tử luồng. Đây là một ví dụ điển hình:
Integer[] arr = {1, 2, 3, 4, 5};

Stream<Integer> numStream = Arrays.stream(arr);
Mã này sẽ tạo ra một numStream chứa nội dung của một mảng có tên là arr, là một mảng số nguyên.

5. Hợp nhất hai luồng hiện có bằng phương thức Stream.concat()

Một phương thức khác có thể được sử dụng để tạo luồng là phương thức Stream.concat() . Nó được sử dụng để kết hợp hai luồng để tạo một luồng duy nhất. Cả hai luồng được kết hợp theo thứ tự. Điều này có nghĩa là luồng đầu tiên xuất hiện trước, tiếp theo là luồng thứ hai, v.v. Một ví dụ về nối như thế này:
Stream<Integer> numStream1 = Stream.of(1, 2, 3, 4, 5);

Stream<Integer> numStream2 = Stream.of(1, 2, 3);

Stream<Integer> combinedStream = Stream.concat( numStream1, numStream2);
Câu lệnh trên sẽ tạo luồng cuối cùng có tên là tổ hợpStream chứa từng phần tử của luồng đầu tiên numStream1 và luồng thứ hai numStream2 .

Các loại hoạt động với Java Stream

Như đã đề cập, bạn có thể thực hiện hai loại hoạt động với Java Stream trong Java 8: trung gian và thiết bị đầu cuối. Chúng ta hãy xem xét từng người trong số họ chi tiết hơn.

Hoạt động trung gian

Các thao tác trung gian tạo ra luồng đầu ra và chỉ được thực thi khi gặp thao tác đầu cuối. Điều này có nghĩa là các hoạt động trung gian được thực hiện một cách lười biếng, theo đường dẫn và chỉ có thể được hoàn thành bằng thao tác đầu cuối. Bạn sẽ tìm hiểu về đánh giá lười biếng và quy trình hóa sau này. Ví dụ về các hoạt động trung gian là các phương thức sau: filter() , map() , Different() , eek() , sort() và một số phương thức khác.

Hoạt động của nhà ga

Các hoạt động của thiết bị đầu cuối hoàn thành việc thực hiện các hoạt động trung gian và cũng trả về kết quả cuối cùng của luồng đầu ra. Bởi vì các hoạt động của thiết bị đầu cuối báo hiệu sự kết thúc của quá trình thực thi lười biếng và đường dẫn, luồng này không thể được sử dụng lại sau khi nó đã trải qua một hoạt động cuối cùng. Ví dụ về hoạt động của thiết bị đầu cuối là các phương thức sau: forEach() , coll() , count() , less() v.v.

Ví dụ về các thao tác với Java Stream

Hoạt động trung gian

Dưới đây là một số ví dụ về một số thao tác trung gian có thể được áp dụng cho Luồng Java:

lọc()

Phương thức này được sử dụng để lọc các phần tử từ luồng khớp với một vị từ cụ thể trong Java. Những mục được lọc này sau đó sẽ tạo thành một luồng mới. Chúng ta hãy xem một ví dụ để hiểu rõ hơn.
Stream<Integer> numStream = Stream.of(43, 65, 1, 98, 63); List<Integer> even = numStream.filter(n -> n % 2 == 0) .collect(Collectors.toList()); System.out.println(even);
Phần kết luận:
[98]
Giải thích: Trong ví dụ này, bạn có thể thấy rằng các phần tử chẵn (chia hết cho 2) được lọc bằng phương thức filter() và được lưu trữ trong danh sách số nguyên numStream , nội dung của danh sách này sẽ được in sau. Vì 98 là số nguyên chẵn duy nhất trong luồng nên nó được in ở đầu ra.

bản đồ()

Phương thức này được sử dụng để tạo luồng mới bằng cách thực thi các hàm được ánh xạ trên các phần tử của luồng đầu vào ban đầu. Có lẽ luồng mới có kiểu dữ liệu khác. Ví dụ trông như thế này:
Stream<Integer> numStream = Stream.of(43, 65, 1, 98, 63); List<Integer> d = numStream.map(n -> n*2) .collect(Collectors.toList()); System.out.println(d);
Phần kết luận:
[86, 130, 2, 196, 126]
Giải thích: Ở đây chúng ta thấy rằng phương thức map() được sử dụng để nhân đôi từng phần tử của luồng numStream . Như bạn có thể thấy từ kết quả đầu ra, mỗi phần tử trong luồng đã được nhân đôi thành công.

riêng biệt()

Phương pháp này được sử dụng để chỉ truy xuất các phần tử riêng lẻ trong luồng bằng cách lọc ra các phần tử trùng lặp. Một ví dụ tương tự trông như thế này:
Stream<Integer> numStream = Stream.of(43,65,1,98,63,63,1); List<Integer> numList = numStream.distinct() .collect(Collectors.toList()); System.out.println(numList);
Phần kết luận:
[43, 65, 1, 98, 63]
Giải thích: Trong trường hợp này, phương thức Different() được sử dụng cho numStream . Nó truy xuất tất cả các phần tử riêng lẻ trong numList bằng cách xóa các phần tử trùng lặp khỏi luồng. Như bạn có thể thấy từ đầu ra, không có bản sao nào, không giống như luồng đầu vào, ban đầu có hai bản sao (63 và 1).

nhìn trộm()

Phương pháp này được sử dụng để theo dõi các thay đổi trung gian trước khi thực hiện thao tác đầu cuối. Điều này có nghĩa là seek() có thể được sử dụng để thực hiện một thao tác trên từng thành phần của luồng nhằm tạo luồng trên đó có thể thực hiện các thao tác trung gian tiếp theo.
Stream<Integer> numStream = Stream.of(43, 65, 1, 98, 63); List<Integer> nList = numStream.map(n -> n*10) .peek(n->System.out.println("Mapped: "+ n)) .collect(Collectors.toList()); System.out.println(nList);
Phần kết luận:
Đã ánh xạ: 430 Đã ánh xạ: 650 Đã ánh xạ: 10 Đã ánh xạ: 980 Đã ánh xạ: 630 [430, 650, 10, 980, 630]
Giải thích: Ở đây, phương thức seek() được sử dụng để tạo ra các kết quả trung gian vì phương thức map() được áp dụng cho các phần tử của luồng. Ở đây, chúng ta có thể nhận thấy rằng ngay cả trước khi sử dụng thao tác đầu cuối coll() để in nội dung cuối cùng của danh sách trong câu lệnh in , kết quả cho mỗi ánh xạ phần tử luồng được in trước một cách tuần tự.

được sắp xếp()

Phương thức được sắp xếp() được sử dụng để sắp xếp các phần tử của luồng. Theo mặc định, nó sắp xếp các phần tử theo thứ tự tăng dần. Bạn cũng có thể chỉ định thứ tự sắp xếp cụ thể làm tham số. Một ví dụ thực hiện phương pháp này trông như thế này:
Stream<Integer> numStream = Stream.of(43, 65, 1, 98, 63); numStream.sorted().forEach(n -> System.out.println(n));
Phần kết luận:
1 43 ​​​​63 65 98
Giải thích: Ở đây, phương thức được sắp xếp () được sử dụng để sắp xếp các phần tử của luồng theo thứ tự tăng dần theo mặc định (vì không có thứ tự cụ thể nào được chỉ định). Bạn có thể thấy các mục được in ở đầu ra được sắp xếp theo thứ tự tăng dần.

Hoạt động của nhà ga

Dưới đây là một số ví dụ về một số thao tác đầu cuối có thể được áp dụng cho luồng Java:

cho mỗi()

Phương thức forEach() được sử dụng để lặp qua tất cả các phần tử của luồng và thực thi từng hàm trên từng phần tử một. Điều này hoạt động như một giải pháp thay thế cho các câu lệnh lặp như for , while và các câu lệnh khác. Ví dụ:
Stream<Integer> numStream = Stream.of(43, 65, 1, 98, 63); numStream.forEach(n -> System.out.println(n));
Phần kết luận:
43 65 1 98 63
Giải thích: Ở đây phương thức forEach() được sử dụng để in từng phần tử của luồng một.

đếm()

Phương thức count() được sử dụng để lấy tổng số phần tử có trong luồng. Nó tương tự như phương thức size() , thường được sử dụng để xác định tổng số phần tử trong một bộ sưu tập. Một ví dụ về việc sử dụng phương thức count() với Luồng Java như sau:
Stream<Integer> numStream = Stream.of(43, 65, 1, 98, 63); System.out.println(numStream.count());
Phần kết luận:
5
Giải thích: Vì numStream chứa 5 phần tử số nguyên nên sử dụng phương thức count() trên đó sẽ cho kết quả là 5.

sưu tầm()

Phương thức coll() được sử dụng để thực hiện việc giảm các phần tử luồng có thể thay đổi. Nó có thể được sử dụng để xóa nội dung khỏi luồng sau khi quá trình xử lý hoàn tất. Nó sử dụng lớp Collector để thực hiện việc giảm bớt .
Stream<Integer> numStream = Stream.of(43, 65, 1, 98, 63); List<Integer> odd = numStream.filter(n -> n % 2 == 1) .collect(Collectors.toList()); System.out.println(odd);
Phần kết luận:
[43, 65, 1, 63]
Giải thích: Trong ví dụ này, tất cả các phần tử lẻ trong luồng đều được lọc và thu thập/rút gọn thành danh sách có tên lẻ . Cuối cùng, một danh sách những cái lẻ được in ra.

tối thiểu()tối đa()

Phương thức min() , như tên cho thấy, có thể được sử dụng trên một luồng để tìm phần tử tối thiểu trong đó. Tương tự, phương thức max() có thể được sử dụng để tìm phần tử lớn nhất trong luồng. Hãy thử hiểu cách chúng có thể được sử dụng bằng một ví dụ:
Stream<Integer> numStream = Stream.of(43, 65, 1, 98, 63); int smallest = numStream.min((m, n) -> Integer.compare(m, n)).get(); System.out.println("Smallest element: " + smallest);
numStream = Stream.of(43, 65, 1, 98, 63); int largest = numStream.max((m, n) -> Integer.compare(m, n)).get(); System.out.println("Largest element: " + largest);
Phần kết luận:
Phần tử nhỏ nhất: 1 Phần tử lớn nhất: 98
Giải thích: Trong ví dụ này, chúng tôi đã in phần tử nhỏ nhất trong numStream bằng phương thức min () và phần tử lớn nhất bằng phương thức max() . Lưu ý rằng ở đây, trước khi sử dụng phương thức max() , chúng ta đã thêm lại các phần tử vào numStream . Điều này là do min() là thao tác cuối cùng và hủy nội dung của luồng ban đầu, chỉ trả về kết quả cuối cùng (trong trường hợp này là số nguyên “nhỏ nhất”).

findAny()findFirst()

findAny() trả về bất kỳ phần tử nào của luồng dưới dạng Tùy chọn . Nếu luồng trống, nó cũng sẽ trả về giá trị Tùy chọn , giá trị này sẽ trống. findFirst() trả về phần tử đầu tiên của luồng dưới dạng Tùy chọn . Giống như phương thức findAny() , phương thức findFirst() cũng trả về một tham số Tùy chọn trống nếu luồng tương ứng trống. Chúng ta hãy xem ví dụ sau dựa trên các phương pháp này:
Stream<Integer> numStream = Stream.of(43, 65, 1, 98, 63); Optional<Integer> opt = numStream.findFirst();System.out.println(opt); numStream = Stream.empty(); opt = numStream.findAny();System.out.println(opt);
Phần kết luận:
Tùy chọn[43] Tùy chọn.empty
Giải thích: Ở đây, trong trường hợp đầu tiên, phương thức findFirst() trả về phần tử đầu tiên của luồng dưới dạng Tùy chọn . Sau đó, khi luồng được gán lại thành một luồng trống, phương thức findAny() trả về một Tùy chọn trống .

allMatch() , AnyMatch()noneMatch()

Phương thức allMatch() được sử dụng để kiểm tra xem tất cả các phần tử trong luồng có khớp với một vị từ nhất định hay không và trả về giá trị boolean true nếu chúng khớp với nhau, nếu không thì trả về false . Nếu luồng trống, nó sẽ trả về true . Phương thức AnyMatch() được sử dụng để kiểm tra xem bất kỳ phần tử nào trong luồng có khớp với một vị từ nhất định hay không. Nó trả về true nếu có, ngược lại là false . Nếu luồng trống, nó sẽ trả về false . Phương thức noneMatch() trả về true nếu không có phần tử nào trong luồng khớp với vị từ và trả về false nếu không. Một ví dụ để minh họa điều này trông như thế này:
Stream<Integer> numStream = Stream.of(43, 65, 1, 98, 63); boolean flag = numStream.allMatch(n -> n1); System.out.println(flag); numStream = Stream.of(43, 65, 1, 98, 63); flag = numStream.anyMatch(n -> n1); System.out.println(flag); numStream = Stream.of(43, 65, 1, 98, 63); flag = numStream.noneMatch(n -> n==1);System.out.println(flag);
Phần kết luận:
sai đúng sai
Giải thích: Đối với luồng numStream chứa 1 làm phần tử, phương thức allMatch() trả về sai vì tất cả các phần tử không phải là 1 mà chỉ một trong số chúng là 1. Phương thức AnyMatch() trả về true vì ít nhất một trong các phần tử là 1. Phương thức noneMatch() trả về false vì 1 thực sự tồn tại dưới dạng một phần tử trong luồng này.

Đánh giá lười biếng trong luồng Java

Đánh giá lười biếng dẫn đến tối ưu hóa khi làm việc với Luồng Java trong Java 8. Chúng chủ yếu liên quan đến việc trì hoãn các hoạt động trung gian cho đến khi gặp phải thao tác đầu cuối. Đánh giá lười biếng có trách nhiệm ngăn chặn sự lãng phí tài nguyên không cần thiết khi tính toán cho đến khi thực sự cần kết quả. Luồng đầu ra từ các hoạt động trung gian chỉ được tạo sau khi hoạt động đầu cuối hoàn thành. Đánh giá lười biếng hoạt động với tất cả các hoạt động trung gian trong luồng Java. Việc sử dụng đánh giá lười biếng rất hữu ích xảy ra khi làm việc với các luồng vô hạn. Bằng cách này, rất nhiều quá trình xử lý không cần thiết sẽ được ngăn chặn.

Đường dẫn trong luồng Java

Một quy trình trong Luồng Java bao gồm một luồng đầu vào, không hoặc nhiều hoạt động trung gian được xếp hàng lần lượt và cuối cùng là hoạt động đầu cuối. Các hoạt động trung gian trong Luồng Java được thực hiện một cách lười biếng. Điều này làm cho các hoạt động trung gian theo đường ống là không thể tránh khỏi. Với các đường ống, về cơ bản là các hoạt động trung gian được kết hợp theo thứ tự, việc thực thi lười biếng trở nên khả thi. Đường ống giúp theo dõi các hoạt động trung gian cần được thực hiện sau khi gặp phải hoạt động cuối cùng.

Phần kết luận

Bây giờ chúng ta hãy tóm tắt những gì chúng ta đã học được ngày hôm nay. Trong bài viết này:
  1. Chúng ta đã xem nhanh Java Stream là gì.
  2. Sau đó, chúng tôi đã học được nhiều kỹ thuật khác nhau để tạo các luồng Java trong Java 8.
  3. Chúng ta đã tìm hiểu hai loại hoạt động chính (các hoạt động trung gian và các hoạt động đầu cuối) có thể được thực hiện trên các luồng Java.
  4. Sau đó chúng tôi xem xét chi tiết một số ví dụ về cả hoạt động trung gian và hoạt động cuối cùng.
  5. Cuối cùng, chúng tôi đã tìm hiểu thêm về đánh giá lười biếng và cuối cùng là tìm hiểu về đường dẫn trong các luồng Java.
Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION