Bài 6: Stream API và Collection
I. Giới Thiệu
Stream API được giới thiệu trong Java 8, cung cấp một cách tiếp cận chức năng để xử lý các tập hợp dữ liệu. Stream cho phép thực hiện các thao tác như lọc, ánh xạ, và giảm trên các collection một cách dễ dàng và hiệu quả.
II. Các Khái Niệm Cơ Bản Về Stream
Stream: Một trình tự các phần tử hỗ trợ các hoạt động kiểu pipeline (chẳng hạn như map-reduce).
Pipeline: Một chuỗi các hoạt động được thực hiện trên một stream. Một pipeline bao gồm nguồn dữ liệu, các hoạt động trung gian và hoạt động kết thúc.
Intermediate Operations: Các hoạt động trung gian chuyển đổi stream thành một stream khác. Ví dụ:
filter,map.Terminal Operations: Các hoạt động kết thúc chuyển đổi stream thành kết quả hoặc hiệu ứng phụ. Ví dụ:
collect,forEach.
III. Các Phương Thức Chính Trong Stream API
filter(Predicate<? super T> predicate)
Lọc các phần tử của stream dựa trên điều kiện của
predicate.Ví dụ:
import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; public class StreamFilterExample { public static void main(String[] args) { List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David"); List<String> filteredNames = names.stream() .filter(name -> name.startsWith("A")) .collect(Collectors.toList()); System.out.println(filteredNames); // Output: [Alice] } }
map(Function<? super T, ? extends R> mapper)
Ánh xạ các phần tử của stream thành các phần tử khác dựa trên hàm
mapper.Ví dụ:
import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; public class StreamMapExample { public static void main(String[] args) { 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] } }
collect(Collector<? super T, A, R> collector)
Thu thập các phần tử của stream thành một collection hoặc một loại kết quả khác.
Ví dụ:
import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; public class StreamCollectExample { public static void main(String[] args) { List<String> names = Arrays.asList("Alice", "Bob", "Charlie"); List<String> uppercasedNames = names.stream() .map(String::toUpperCase) .collect(Collectors.toList()); System.out.println(uppercasedNames); // Output: [ALICE, BOB, CHARLIE] } }
forEach(Consumer<? super T> action)
Thực hiện hành động
actiontrên mỗi phần tử của stream.Ví dụ:
import java.util.Arrays; import java.util.List; public class StreamForEachExample { public static void main(String[] args) { List<String> names = Arrays.asList("Alice", "Bob", "Charlie"); names.stream() .forEach(System.out::println); // Output: // Alice // Bob // Charlie } }
reduce(BinaryOperator accumulator)
Giảm các phần tử của stream thành một kết quả duy nhất bằng cách sử dụng hàm tích lũy
accumulator.Ví dụ:
import java.util.Arrays; import java.util.List; public class StreamReduceExample { public static void main(String[] args) { List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5); int sum = numbers.stream() .reduce(0, Integer::sum); System.out.println("Sum: " + sum); // Output: Sum: 15 } }
IV. Ví Dụ Minh Họa Chi Tiết
Sử Dụng Stream để Lọc và Ánh Xạ Dữ Liệu
Mô tả: Lọc danh sách các sản phẩm để chỉ lấy những sản phẩm có giá lớn hơn 100, sau đó ánh xạ để lấy tên sản phẩm.
import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; class Product { String name; double price; Product(String name, double price) { this.name = name; this.price = price; } public String getName() { return name; } public double getPrice() { return price; } } public class StreamExample { public static void main(String[] args) { List<Product> products = Arrays.asList( new Product("Laptop", 1200), new Product("Smartphone", 800), new Product("Tablet", 200), new Product("Mouse", 50) ); List<String> expensiveProductNames = products.stream() .filter(product -> product.getPrice() > 100) .map(Product::getName) .collect(Collectors.toList()); System.out.println(expensiveProductNames); // Output: [Laptop, Smartphone, Tablet] } }Sử Dụng Stream để Tính Tổng và Trung Bình
Mô tả: Tính tổng và trung bình giá trị của các số trong danh sách.
import java.util.Arrays; import java.util.IntSummaryStatistics; import java.util.List; public class StreamStatisticsExample { public static void main(String[] args) { List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); IntSummaryStatistics stats = numbers.stream() .mapToInt(Integer::intValue) .summaryStatistics(); System.out.println("Sum: " + stats.getSum()); // Output: Sum: 55 System.out.println("Average: " + stats.getAverage()); // Output: Average: 5.5 } }Sử Dụng Stream để Nhóm Dữ Liệu
Mô tả: Nhóm các sinh viên theo điểm số.
import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.stream.Collectors; class Student { String name; int score; Student(String name, int score) { this.name = name; this.score = score; } public String getName() { return name; } public int getScore() { return score; } } public class StreamGroupingExample { public static void main(String[] args) { List<Student> students = Arrays.asList( new Student("Alice", 85), new Student("Bob", 75), new Student("Charlie", 85), new Student("David", 75) ); Map<Integer, List<Student>> studentsByScore = students.stream() .collect(Collectors.groupingBy(Student::getScore)); studentsByScore.forEach((score, studentList) -> { System.out.println("Score: " + score); studentList.forEach(student -> System.out.println(" " + student.getName())); }); // Output: // Score: 85 // Alice // Charlie // Score: 75 // Bob // David } }Sử Dụng Stream để Thực Hiện Các Phép Toán Phức Tạp
Mô tả: Tính tổng các số chẵn và nhân đôi các số lẻ trong danh sách.
import java.util.Arrays; import java.util.List; public class StreamComplexOperationsExample { public static void main(String[] args) { List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); int sumOfEvens = numbers.stream() .filter(n -> n % 2 == 0) .reduce(0, Integer::sum); List<Integer> doubledOdds = numbers.stream() .filter(n -> n % 2 != 0) .map(n -> n * 2) .collect(Collectors.toList()); System.out.println("Sum of evens: " + sumOfEvens); // Output: Sum of evens: 30 System.out.println("Doubled odds: " + doubledOdds); // Output: Doubled odds: [2, 6, 10, 14, 18] } }
V. Bài Tập Thực Hành
- **Tạo chương trình Java sử dụng Stream API để lọc danh sách các
nhân viên có tuổi lớn hơn 30 và ánh xạ để lấy tên của họ.**
Yêu cầu: Lọc và ánh xạ danh sách các nhân viên để lấy tên của những người có tuổi lớn hơn 30.
Ví dụ:
import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; class Employee { String name; int age; Employee(String name, int age) { this.name = name; this.age = age; } public String getName() { return name; } public int getAge() { return age; } } public class EmployeeFilterExample { public static void main(String[] args) { List<Employee> employees = Arrays.asList( new Employee("John", 25), new Employee("Jane", 35), new Employee("Jack", 45) ); List<String> employeeNames = employees.stream() .filter(e -> e.getAge() > 30) .map(Employee::getName) .collect(Collectors.toList()); System.out.println("Employees over 30: " + employeeNames); // Output: [Jane, Jack] } }
Tạo chương trình Java sử dụng Stream API để tính tổng điểm số của sinh viên và trung bình điểm số.
Yêu cầu: Tính tổng và trung bình điểm số của sinh viên.
Ví dụ:
import java.util.Arrays; import java.util.IntSummaryStatistics; import java.util.List; class Student { String name; int score; Student(String name, int score) { this.name = name; this.score = score; } public int getScore() { return score; } } public class StudentStatisticsExample { public static void main(String[] args) { List<Student> students = Arrays.asList( new Student("Alice", 85), new Student("Bob", 75), new Student("Charlie", 90) ); IntSummaryStatistics stats = students.stream() .mapToInt(Student::getScore) .summaryStatistics(); System.out.println("Total score: " + stats.getSum()); // Output: Total score: 250 System.out.println("Average score: " + stats.getAverage()); // Output: Average score: 83.33333333333333 } }
Tạo chương trình Java sử dụng Stream API để nhóm các sản phẩm theo danh mục và tính tổng giá trị của mỗi danh mục.
Yêu cầu: Nhóm các sản phẩm theo danh mục và tính tổng giá trị của mỗi danh mục.
Ví dụ:
import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.stream.Collectors; class Product { String category; double price; Product(String category, double price) { this.category = category; this.price = price; } public String getCategory() { return category; } public double getPrice() { return price; } } public class ProductGroupingExample { public static void main(String[] args) { List<Product> products = Arrays.asList( new Product("Electronics", 1200), new Product("Electronics", 800), new Product("Groceries", 50), new Product("Groceries", 30) ); Map<String, Double> totalPriceByCategory = products.stream() .collect(Collectors.groupingBy( Product::getCategory, Collectors.summingDouble(Product::getPrice) )); totalPriceByCategory.forEach((category, totalPrice) -> { System.out.println("Category: " + category + ", Total Price: " + totalPrice); }); // Output: // Category: Electronics, Total Price: 2000.0 // Category: Groceries, Total Price: 80.0 } }
Tạo chương trình Java sử dụng Stream API để tìm sinh viên có điểm số cao nhất và thấp nhất.
Yêu cầu: Tìm và in ra thông tin của sinh viên có điểm số cao nhất và thấp nhất.
Ví dụ:
import java.util.Arrays; import java.util.List; import java.util.Optional; class Student { String name; int score; Student(String name, int score) { this.name = name; this.score = score; } public String getName() { return name; } public int getScore() { return score; } @Override public String toString() { return name + ": " + score; } } public class StudentMinMaxExample { public static void main(String[] args) { List<Student> students = Arrays.asList( new Student("Alice", 85), new Student("Bob", 75), new Student("Charlie", 90) ); Optional<Student> maxScoreStudent = students.stream() .max((s1, s2) -> Integer.compare(s1.getScore(), s2.getScore())); Optional<Student> minScoreStudent = students.stream() .min((s1, s2) -> Integer.compare(s1.getScore(), s2.getScore())); maxScoreStudent.ifPresent(s -> System.out.println("Highest score: " + s)); // Output: Highest score: Charlie: 90 minScoreStudent.ifPresent(s -> System.out.println("Lowest score: " + s)); // Output: Lowest score: Bob: 75 } }
Tạo chương trình Java sử dụng Stream API để thực hiện các phép toán phức tạp như đếm số lần xuất hiện của từng phần tử trong danh sách.
Yêu cầu: Đếm số lần xuất hiện của từng phần tử trong danh sách và in ra kết quả.
Ví dụ:
import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.stream.Collectors; public class ElementFrequencyExample { public static void main(String[] args) { List<String> words = Arrays.asList("apple", "banana", "apple", "orange", "banana", "apple"); Map<String, Long> frequencyMap = words.stream() .collect(Collectors.groupingBy(w -> w, Collectors.counting())); frequencyMap.forEach((word, count) -> { System.out.println(word + ": " + count); }); // Output: // apple: 3 // banana: 2 // orange: 1 } }
VI. Kết Luận
Trong bài viết này, chúng ta đã khám phá Stream API và cách sử dụng nó để xử lý dữ liệu trong các collection. Hiểu rõ và sử dụng hiệu quả Stream API sẽ giúp bạn viết mã nguồn ngắn gọn, dễ hiểu và hiệu quả hơn. Các ví dụ minh họa và bài tập thực hành sẽ giúp củng cố kiến thức và ứng dụng hiệu quả trong các dự án thực tế.
VII. Tài Liệu Tham Khảo
Java SE Documentation: Java Streams
"Java 8 in Action" by Raoul-Gabriel Urma, Mario Fusco, Alan Mycroft: Một tài liệu tuyệt vời về các tính năng mới của Java 8, bao gồm Stream API.