Java Streams - Sự khác nhau giữa Collections và Streams

Link Source: Java Streams - Sự khác nhau giữa Collections và Streams
Thể loại: Java, Lập trình
Tags: Học hành, Lập trình

Java Streams – Sự khác nhau giữa Collections và Streams



Streams là một bổ sung mới trong thư viện Collections API của Java 8. Java streams API xử lý tuần tự các element cho collection tương tự Linq trong C sharp hay câu query trong SQL.



Xét ví dụ:


Tính tổng lương của tất cả Employee trong query SQL.


Code SQL:


SELECT SUM(salary) FROM Employee

Câu lệnh trên tự động trả về tổng lương của tất cả Employee mà không cần phải thực hiện bất kì tính toán gì ở phía đầu cuối developer(Bình thường thì sẽ SELECT tất cả các Employee, sau đó dùng code Java để duyệt và tính tổng của tất cả Employee).


Tương tự vấn đề trên, khi sử dụng Collections trong Java, chúng ta thực hiện các vòng lặp và thực hiện lại các đoạn kiểm tra. Giả sử muốn tính tổng lương của các employee có role = ‘Developer’ từ 1 danh sách chúng ta phải thực hiện lặp tất cả các phần tử, kiểm tra phần tử đó có role = ‘Developer’ rồi cộng lại.


Code thường:


public static final String ROLEDEVELOPER = "Developer";
public double sumSalaryDeveloper(List<Employee> listEmployee)
double sumSalary = 0;
for (Employee emp : listEmployee)
if (emp.getRole().equals(ROLEDEVELOPER))
sumSalary += emp.getSalary();


return sumSalary;

Code Java Stream:


public static final String ROLEDEVELOPER = "Developer";
public double sumSalaryDeveloper(List<Employee> listEmployee)
return listEmployee.stream().filter(p -> p.getRole().equals(ROLEDEVELOPER)).mapToDouble(p -> p.getSalary()).sum();

Ngắn ngọn, dễ hiểu như query SQL đúng không nào. Như vậy, bạn có thể hiểu stream đại diện cho một collection được xử lý tuần tự các phần tử và hỗ trợ rất nhiều loại operation để tính toán dựa trên những element của collection đó (sum, avg, toMap, toList …)


So sánh sự khác nhau giữa Collections và Streams


Lưu trữ – Java Stream không giống như các Collection không lưu trữ bất kỳ dữ liệu nào.
Tính toán – Các phần tử trong các Collection được tính toán trước khi được thêm vào Collection, trong khi các phần tử stream được tính theo yêu cầu nếu chúng được sử dụng.
Kích thước – Collection chứa số lượng phần tử hữu hạn, trong khi Stream có khả năng vô hạn.
Mức tiêu thụ – Các phần tử trong Stream chỉ có thể được sử dụng một lần, tương tự như iterator. Các phần tử trong Collection có thể được dùng nhiều lần như mong muốn.
Iteration – Việc lặp lại trên các collection được thực hiện bên ngoài (bởi người dùng) trong khi Stream sử dụng lặp lại nội bộ và xử lý việc này cho bạn. Do đó việc debug khá khó khăn trên Streams do việc lặp nội bộ.


Hướng dẫn dùng Streams cơ bản


Tạo 1 empty stream


Method empty() được sử dụng để tạo 1 empty stream:


Stream<String> streamEmpty = Stream.empty();

empty() thường được dùng khi khởi tạo để tránh việc trả về null cho các Stream


public Stream<String> streamOf(List<String> list) 

Tạo stream từ collection (Stream of Collection)


Stream cũng có thể được tạo từ bất kì kiểu collection nào (bao gồm: Collection, List, Set …):


Collection<String> collection = Arrays.asList("a", "b", "c");
Stream<String> streamOfCollection = collection.stream();

Stream of Array


Stream cũng có thể được tạo từ 1 array hoặc 1 phần của array:


Stream<String> streamOfArray = Stream.of("a", "b", "c");
String[] arr = new String[]"a", "b", "c";
Stream<String> streamOfArrayFull = Arrays.stream(arr);
Stream<String> streamOfArrayPart = Arrays.stream(arr, 1, 3);

Stream.builder()


Stream.builder() được sử dụng khi muốn thêm mới 1 phần vào bên phải stream.


Stream<String> streamBuilder =
Stream.<String>builder().add("a").add("b").add("c").build();

Stream.generate()


Phương thức generate() chấp nhận một Supplier<T> cho các phần tử được tạo ra. Số phần tử được tạo ra là vô hạn nên cần phải dùng lệnh .limit() để giới hạn số phần tử được tạo ra.


Ví dụ đoạn code dưới đây sẽ tạo ra liên tục 10 String có giá trị là “shareeverythings.com”


Stream<String> streamGenerated =
Stream.generate(() -> "shareeverythings.com").limit(10);

Stream.iterate()


1 cách khác để tạo ra 1 stream vô hạn là sử dụng method iterate():


Stream<String> streamIterated = Stream.iterate("qlamxmaster", n -> n + 1).limit(5);
List<String> listIterated = streamIterated.collect(Collectors.toList());
System.out.println(listIterated);

Kết quả:



  • [qlamxmaster, qlamxmaster1, qlamxmaster11, qlamxmaster111 …]


Stream of String


Stream<String> streamOfString =
Pattern.compile("_ ").splitAsStream("a, b, c");

Stream of File


Path path = Paths.get("C:\\file.txt");
Stream<String> streamOfStrings = Files.lines(path);
Stream<String> streamWithCharset =
Files.lines(path, Charset.forName("UTF-8"));

Các ví dụ, method thường dùng với Stream


Chuyển 1 Stream sang Collection hoặc array:


  • Chúng ta có thể dùng stream collect() để tạo 1 List, Set, Map từ stream.

Stream<Integer> intStream = Stream.of(1,2,3,4);
List<Integer> intList = intStream.collect(Collectors.toList());
System.out.println(intList); //prints [1, 2, 3, 4]


intStream = Stream.of(1,2,3,4); //stream bị đóng nên cần khởi tạo lại
Map<Integer,Integer> intMap = intStream.collect(Collectors.toMap(i -> i, i -> i+10));
System.out.println(intMap); //prints 1=11, 2=12, 3=13, 4=14


  • Dùng stream toArray() để tạo 1 array từ stream


Stream<Integer> intStream = Stream.of(1,2,3,4);
Integer[] intArray = intStream.toArray(Integer[]::new);
System.out.println(Arrays.toString(intArray)); //prints [1, 2, 3, 4]


Stream.filter()


Stream.filter() Trả về 1 stream đã lọc các phần tử khớp với điều kiện Predicate



Predicate<Integer> p = num -> num % 2 == 0;
List<Integer> list = Arrays.asList(3,4,6);
list.stream().filter(p).forEach(e -> System.out.print(e+" "));


Kết quả:



  • 4 6


Stream.allMatch(), Stream.anyMatch() and Stream.noneMatch()


allMatch(): trả về true nếu tất cả các phần tử khớp với Predicate.
anyMatch(): trả về true nếu có ít nhất 1 phần tử khớp với Predicate.
noneMatch(): trả về true nếu không có phần tử nào khớp với Predicate.


Ví du:



Predicate<Integer> p = num -> num % 2 == 0;
List<Integer> list = Arrays.asList(3,5,6);
System.out.println("allMatch:" + list.stream().allMatch(p));
System.out.println("anyMatch:" + list.stream().anyMatch(p));
System.out.println("noneMatch:" + list.stream().noneMatch(p));


Kết quả:



  • allMatch:false

  • anyMatch:true

  • noneMatch:false


Stream.findAny() and Stream.findFirst()


findAny(): Trả về 1 phần tử bất kì của stream.
findFirst(): Trả về phần tử đầu tiên của stream.


Ví dụ:



List<String> list = Arrays.asList("G","B","F","E");
String any = list.stream().findAny().get();
System.out.println("FindAny: "+ any);
String first = list.stream().findFirst().get();
System.out.println("FindFirst: "+ first);


Kết quả:



  • FindAny: G

  • FindFirst: G


Stream.distinct()


Stream.distinct() trả về 1 stream với các phần tử riêng biệt (không trùng nhau)



List<Integer> list = Arrays.asList(3,4,6,6,4);
System.out.print("Distinct elements: ");
list.stream().distinct().forEach(p -> System.out.print(p + ", "));


Kết quả:



  • Distinct elements: 3, 4, 6,


Stream map()


Sử dụng map() để map (ánh xạ) mỗi phần tử của stream sang 1 giá trị tương ứng. Ví dụ chuyển các string của 1 list sang chữ hoa:


Stream<String> names = Stream.of("qlamxmaster", "Java", "shareeverythings.com");
System.out.println(names.map(s ->
return s.toUpperCase();
).collect(Collectors.toList()));
//prints [QLAMXMASTER, JAVA, SHAREEVERYTHINGS.COM]

Stream.max() and Stream.min()


max(): tìm phần tử có giá trị lớn nhất dựa theo Comparator.
min(): tìm phần tử có giá trị nhỏ nhất dựa theo Comparator.


Ví dụ:



List<String> list = Arrays.asList("G","B","F","E");
String max = list.stream().max(Comparator.comparing(String::valueOf)).get();
System.out.println("Max:"+ max);
String min = list.stream().min(Comparator.comparing(String::valueOf)).get();
System.out.println("Min:"+ min);


Kết quả:



  • Max:G

  • Min:B


Một số lưu ý với Stream Java


Không tái sử dụng(Reuse) với stream: Stream java không thể tái sử dụng lại:



Stream<String> names = Stream.of("qlamxmaster", "java", "shareeverythings.com");
names.forEach(p -> System.out.print(p + " ")); // print: qlamxmaster java shareeverythings.com
names.forEach(p -> System.out.print(p + " ")); // exception: Exception in thread "main" java.lang.IllegalStateException: stream has already been operated upon or closed


Cách xử lý: khởi tạo lại stream hoặc dùng Supplier:



Stream<String> names = Stream.of("qlamxmaster", "java", "shareeverythings.com");
names.forEach(p -> System.out.print(p + " ")); //print: qlamxmaster java shareeverythings.com

// Khởi tạo lại stream
names = Stream.of("qlamxmaster", "java", "shareeverythings.com");
names.forEach(p -> System.out.print(p + " "));  //print: qlamxmaster java shareeverythings.com


// dùng supplier:
Supplier<Stream<String>> streamSupplier = () -> Stream.of("qlamxmaster", "java", "shareeverythings.com");
streamSupplier.get().forEach(p -> System.out.print(p + " ")); //print: qlamxmaster java shareeverythings.com
streamSupplier.get().forEach(p -> System.out.print(p + " ")); //print: qlamxmaster java shareeverythings.com


References: http://bigdatums.net/2017/10/18/introduction-to-java-streams/ | https://stackjava.com/java8/stream-api-la-gi-stream-api-trong-java-8.html



Link Source: Java Streams - Sự khác nhau giữa Collections và Streams
Comment