Stream API là gì? Stream API trong Java 8
1. Stream API là gì?
Stream là 1 lớp trừu tượng mới được giới thiệu trong Java 8. Sử dụng Stream, bạn có thể xử lý dữ liệu 1 cách tự nhiên giống như các câu lệnh SQL. Ví dụ ta có câu SQL sau:
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 Employeesau đó 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. Trong khi nếu muốn xử lý chúng song song lại dễ gặp lỗi.
public static final String ROLE_DEVELOPER = "Developer"; public double sumSalaryOfDeveloper(List<Employee> listEmployee) { double sumSalary = 0; for (Employee emp : listEmployee) { if (emp.getRole().equals(ROLE_DEVELOPER)) { sumSalary += emp.getSalary(); } } return sumSalary; }
Để giải quyết vấn đề đó, Java 8 giới thiệu Stream API giúp developer xử lý dữ liệu khai báo và tận dụng kiến trúc đa lõi (multicore) mà không cần viết mã cụ thể cho nó.
public static final String ROLE_DEVELOPER = "Developer"; public double sumSalaryOfDeveloper(List<Employee> listEmployee) { return listEmployee.stream().filter(p -> p.getRole().equals(ROLE_DEVELOPER)).mapToDouble(p -> p.getSalary()).sum(); }
Như vậy, bạn có thể hiểu stream đại diện cho một collection được xử lý tuầ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 đó (tính tổng, convert sang map, …)
2. Cách tạo 1 Stream
2.1 Tạo 1 empty stream
Method empty()
được sử dụng để tạo 1 empty stream:
Stream<String> streamEmpty = Stream.empty();
empty stream trường được dùng khi khởi tạo để tránh việc trả về null cho các stream không có phần tử nào:
public Stream<String> streamOf(List<String> list) { return list == null || list.isEmpty() ? Stream.empty() : list.stream(); }
2.2 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 (Collection, List, Set):
Collection<String> collection = Arrays.asList("a", "b", "c"); Stream<String> streamOfCollection = collection.stream();
2.3 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);
2.4 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();
2.5 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à “stackjava”
Stream<String> streamGenerated = Stream.generate(() -> "stackjava").limit(10);
2.6 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("StackJava", n -> n + 1).limit(5); List<String> listIterated = streamIterated.collect(Collectors.toList()); System.out.println(listIterated);
Kết quả:
[StackJava, StackJava1, StackJava11, StackJava111, StackJava1111]
2.7 Stream of Primitives
IntStream intStream = IntStream.range(1, 3);
2.8 Stream of String
Stream<String> streamOfString = Pattern.compile(", ").splitAsStream("a, b, c");
2.9. 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"));
3. Các ví dụ, method thường dùng với Stream
3.1 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 (*đọc phần lưu ý cuối bà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]
3.2 Stream.filter()
Stream.filter()
Trả về 1 stream viows các phần tử khớp với 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
3.3 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
3.4 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
3.5 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,
3.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("stack", "Java", "stackjava.com"); System.out.println(names.map(s -> { return s.toUpperCase(); }).collect(Collectors.toList())); //prints [STACK, JAVA, STACKJAVA.COM]
3.7 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 theoComparator
.
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
List<String> list = Arrays.asList("A","B","C"); list.stream().peek(s->System.out.println(s+s)).collect(Collectors.toList());
Kết quả:
AA BB CC
Trả về 1 new stream gồm tất cả các phần tử của stream sau khi áp dụng Consumer
.
Ví dụ:
* Một số lưu ý với Stream
Reuse với stream: stream không thể reuse lại:
Stream<String> names = Stream.of("stack", "java", "stackjava.com"); names.forEach(p -> System.out.print(p + " ")); // print: stack java stackjava.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("stack", "java", "stackjava.com"); names.forEach(p -> System.out.print(p + " ")); // print: stack java stackjava.com // Khởi tạo lại stream names = Stream.of("stack", "java", "stackjava.com"); names.forEach(p -> System.out.print(p + " ")); // print: stack java stackjava.com // dùng supplier: Supplier<Stream<String>> streamSupplier = () -> Stream.of("stack", "java", "stackjava.com"); streamSupplier.get().forEach(p -> System.out.print(p + " ")); // print: stack java stackjava.com streamSupplier.get().forEach(p -> System.out.print(p + " ")); // print: stack java stackjava.com
References: