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