Bài 3: Streams API (Java 8)
Mục tiêu
Bài viết này nhằm mục đích giúp bạn hiểu và sử dụng Streams API để xử lý dữ liệu trong Java 8. Chúng ta sẽ xem xét cách tạo Streams, các thao tác cơ bản với Streams, và các ví dụ thực tế.
Nội dung
1. Giới thiệu về Streams
1.1 Định nghĩa và lợi ích
Định nghĩa:
- Streams là một chuỗi các phần tử được tính toán theo yêu cầu. Chúng cung cấp các phương thức giúp xử lý dữ liệu dễ dàng và hiệu quả, từ các thao tác đơn giản như lọc và sắp xếp, đến các thao tác phức tạp như nhóm và tổng hợp.
Lợi ích:
Cú pháp ngắn gọn: Streams cho phép viết mã ngắn gọn, dễ đọc và dễ hiểu hơn.
Tăng hiệu suất: Streams có thể thực hiện các thao tác song song (parallel processing) để tăng hiệu suất.
Không làm thay đổi nguồn dữ liệu: Streams không thay đổi dữ liệu gốc mà tạo ra một phiên bản mới của dữ liệu sau khi xử lý.
Tối ưu hóa bộ nhớ: Streams không lưu trữ dữ liệu mà chỉ xử lý chúng khi cần thiết.
1.2 Cách tạo Streams
Tạo Streams từ các bộ sưu tập:
List<String> list = Arrays.asList("a", "b", "c"); Stream<String> stream = list.stream();Ví dụ này tạo một Stream từ một danh sách.
Tạo Streams từ mảng:
String[] array = {"a", "b", "c"}; Stream<String> stream = Arrays.stream(array);Ví dụ này tạo một Stream từ một mảng.
Tạo Streams từ giá trị:
Stream<String> stream = Stream.of("a", "b", "c");Ví dụ này tạo một Stream từ các giá trị riêng lẻ.
Tạo Streams từ tệp:
Path path = Paths.get("file.txt"); try (Stream<String> lines = Files.lines(path)) { lines.forEach(System.out::println); } catch (IOException e) { e.printStackTrace(); }Ví dụ này tạo một Stream từ các dòng của một tệp văn bản.
2. Các thao tác cơ bản với Streams
2.1 filter()
Mục đích: Lọc các phần tử thỏa mãn điều kiện.
Ví dụ:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5); List<Integer> evenNumbers = numbers.stream() .filter(n -> n % 2 == 0) .collect(Collectors.toList()); System.out.println(evenNumbers); // Output: [2, 4]
2.2 map()
Mục đích: Biến đổi các phần tử trong stream.
Ví dụ:
List<String> names = Arrays.asList("Alice", "Bob", "Charlie"); List<Integer> nameLengths = names.stream() .map(String::length) .collect(Collectors.toList()); System.out.println(nameLengths); // Output: [5, 3, 7]
2.3 flatMap()
Mục đích: Biến đổi mỗi phần tử thành một Stream và sau đó gộp tất cả các Stream này lại thành một Stream duy nhất.
Ví dụ:
List<List<String>> list = Arrays.asList( Arrays.asList("a", "b"), Arrays.asList("c", "d") ); List<String> flatList = list.stream() .flatMap(Collection::stream) .collect(Collectors.toList()); System.out.println(flatList); // Output: [a, b, c, d]
2.4 reduce()
Mục đích: Tổng hợp các phần tử thành một kết quả duy nhất.
Ví dụ:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5); int sum = numbers.stream() .reduce(0, Integer::sum); System.out.println(sum); // Output: 15
2.5 collect()
Mục đích: Thu thập các phần tử vào một bộ sưu tập hoặc cấu trúc dữ liệu khác.
Ví dụ:
List<String> list = Arrays.asList("a", "b", "c"); Set<String> set = list.stream() .collect(Collectors.toSet()); System.out.println(set); // Output: [a, b, c]
2.6 forEach()
Mục đích: Thực hiện một hành động cho mỗi phần tử.
Ví dụ:
List<String> list = Arrays.asList("a", "b", "c"); list.stream() .forEach(System.out::println);
3. Các thao tác nâng cao với Streams
3.1 sorted()
Mục đích: Sắp xếp các phần tử.
Ví dụ:
List<String> list = Arrays.asList("Charlie", "Alice", "Bob"); List<String> sortedList = list.stream() .sorted() .collect(Collectors.toList()); System.out.println(sortedList); // Output: [Alice, Bob, Charlie]
3.2 distinct()
Mục đích: Loại bỏ các phần tử trùng lặp.
Ví dụ:
List<Integer> numbers = Arrays.asList(1, 2, 2, 3, 3, 3); List<Integer> distinctNumbers = numbers.stream() .distinct() .collect(Collectors.toList()); System.out.println(distinctNumbers); // Output: [1, 2, 3]
3.3 limit() và skip()
Mục đích: Giới hạn số lượng phần tử và bỏ qua một số phần tử.
Ví dụ:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5); List<Integer> limitedNumbers = numbers.stream() .limit(3) .collect(Collectors.toList()); System.out.println(limitedNumbers); // Output: [1, 2, 3] List<Integer> skippedNumbers = numbers.stream() .skip(2) .collect(Collectors.toList()); System.out.println(skippedNumbers); // Output: [3, 4, 5]
4. Ví dụ thực tế
4.1 Xử lý danh sách đối tượng
Giả sử chúng ta có một lớp
Person:public class Person { private String name; private int age; public Person(String name, int age) { this.name = name; this.age = age; } public String getName() { return name; } public int getAge() { return age; } @Override public String toString() { return "Person{name='" + name + "', age=" + age + "}"; } }Tìm danh sách những người trên 18 tuổi:
List<Person> people = Arrays.asList( new Person("Alice", 30), new Person("Bob", 20), new Person("Charlie", 15) ); List<Person> adults = people.stream() .filter(p -> p.getAge() > 18) .collect(Collectors.toList()); System.out.println(adults); // Output: [Person{name='Alice', age=30}, Person{name='Bob', age=20}]
4.2 Kết hợp nhiều thao tác Streams
Lọc, biến đổi và thu thập:
List<String> names = Arrays.asList("Alice", "Bob", "Charlie"); List<String> upperCaseNames = names.stream() .filter(name -> name.startsWith("A")) .map(String::toUpperCase) .collect(Collectors.toList()); System.out.println(upperCaseNames); // Output: [ALICE]
4.3 Sử dụng Streams để đọc tệp
Đọc tất cả các dòng từ một tệp và lọc những dòng chứa một từ cụ thể:
Path path = Paths.get("file.txt"); try (Stream<String> lines = Files.lines(path)) { List<String> filteredLines = lines.filter(line -> line.contains("keyword")) .collect(Collectors.toList()); System.out.println(filteredLines); // Output: List of lines containing "keyword" } catch (IOException e) { e.printStackTrace(); }
Kết luận
Streams API trong Java 8 mang lại một cách tiếp cận mạnh mẽ và hiệu quả để xử lý dữ liệu. Với khả năng hỗ trợ lập trình hàm, Streams giúp mã nguồn trở nên ngắn gọn
, dễ đọc và dễ bảo trì hơn. Việc hiểu và sử dụng thành thạo Streams API sẽ giúp bạn tận dụng tối đa sức mạnh của Java 8 trong các ứng dụng của mình.