Bài 2: Các Functional Interface phổ biến trong Java 8
Mục tiêu:
Giới thiệu chi tiết về các Functional Interface (FI) có sẵn trong Java 8.
Giúp người đọc hiểu cách sử dụng các FI phổ biến như
Predicate,Function,Consumer, vàSupplier.Phân biệt các FI chuyên biệt và lợi ích của chúng.
Dàn ý chi tiết:
1. Nhóm FI cơ bản
Các Functional Interface cơ bản trong Java 8 được định nghĩa trong gói java.util.function. Chúng được thiết kế để hỗ trợ các tác vụ phổ biến như kiểm tra điều kiện, ánh xạ dữ liệu, thực hiện hành động, và cung cấp giá trị.
Predicate<T>: Kiểm tra điều kiệnĐịnh nghĩa:
Predicate<T>là một FI có phương thứctest(T t)trả về giá trị boolean.Công dụng: Dùng để kiểm tra một điều kiện nào đó trên đối tượng kiểu
T.Ví dụ:
Predicate<Integer> isEven = num -> num % 2 == 0; System.out.println(isEven.test(4)); // true System.out.println(isEven.test(5)); // falseỨng dụng: Thường dùng trong
filter()của Stream để lọc dữ liệu.
Function<T, R>: Ánh xạ dữ liệuĐịnh nghĩa:
Function<T, R>là một FI có phương thứcapply(T t)trả về giá trị kiểuR.Công dụng: Dùng để chuyển đổi dữ liệu từ kiểu
Tsang kiểuR.Ví dụ:
Function<String, Integer> stringLength = str -> str.length(); System.out.println(stringLength.apply("Java")); // 4Ứng dụng: Thường dùng trong
map()của Stream để biến đổi dữ liệu.
Consumer<T>: Thực hiện hành độngĐịnh nghĩa:
Consumer<T>là một FI có phương thứcaccept(T t)không trả về giá trị.Công dụng: Dùng để thực hiện một hành động trên đối tượng kiểu
T.Ví dụ:
Consumer<String> printMessage = message -> System.out.println(message); printMessage.accept("Hello, World!"); // Hello, World!Ứng dụng: Thường dùng trong
forEach()của Stream để thực hiện hành động trên từng phần tử.
Supplier<T>: Cung cấp giá trịĐịnh nghĩa:
Supplier<T>là một FI có phương thứcget()trả về giá trị kiểuT.Công dụng: Dùng để cung cấp giá trị mà không cần đầu vào.
Ví dụ:
Supplier<Double> randomValue = () -> Math.random(); System.out.println(randomValue.get()); // Giá trị ngẫu nhiên từ 0.0 đến 1.0Ứng dụng: Thường dùng để tạo Stream từ một nguồn dữ liệu động.
2. Nhóm FI chuyên biệt
Java 8 cung cấp các FI chuyên biệt để tối ưu hóa hiệu suất khi làm việc với các kiểu dữ liệu nguyên thủy (primitive types) như int, double, long, v.v. Các FI này giúp tránh việc auto-boxing/unboxing, từ đó cải thiện hiệu suất.
Primitive Specialization
IntPredicate,DoublePredicate,LongPredicate:Tương tự
Predicate<T>nhưng làm việc trực tiếp với kiểuint,double,long.Ví dụ:
IntPredicate isPositive = num -> num > 0; System.out.println(isPositive.test(10)); // true
IntFunction<R>,DoubleFunction<R>,LongFunction<R>:Tương tự
Function<T, R>nhưng làm việc trực tiếp với kiểuint,double,long.Ví dụ:
IntFunction<String> intToString = num -> "Number: " + num; System.out.println(intToString.apply(42)); // Number: 42
IntConsumer,DoubleConsumer,LongConsumer:Tương tự
Consumer<T>nhưng làm việc trực tiếp với kiểuint,double,long.Ví dụ:
IntConsumer printSquare = num -> System.out.println(num * num); printSquare.accept(5); // 25
IntSupplier,DoubleSupplier,LongSupplier:Tương tự
Supplier<T>nhưng làm việc trực tiếp với kiểuint,double,long.Ví dụ:
IntSupplier randomInt = () -> (int) (Math.random() * 100); System.out.println(randomInt.get()); // Giá trị ngẫu nhiên từ 0 đến 99
Multi-arguments FI
BiPredicate<T, U>:Tương tự
Predicate<T>nhưng nhận hai đối số.Ví dụ:
BiPredicate<String, Integer> isLengthEqual = (str, len) -> str.length() == len; System.out.println(isLengthEqual.test("Java", 4)); // true
BiFunction<T, U, R>:Tương tự
Function<T, R>nhưng nhận hai đối số.Ví dụ:
BiFunction<Integer, Integer, Integer> add = (a, b) -> a + b; System.out.println(add.apply(5, 3)); // 8
BiConsumer<T, U>:Tương tự
Consumer<T>nhưng nhận hai đối số.Ví dụ:
BiConsumer<String, Integer> printDetails = (name, age) -> System.out.println(name + " is " + age + " years old."); printDetails.accept("Alice", 25); // Alice is 25 years old.
3. So sánh ưu/nhược
Ưu điểm của Primitive Specialization:
Tránh auto-boxing/unboxing, giúp cải thiện hiệu suất.
Phù hợp khi làm việc với dữ liệu nguyên thủy.
Nhược điểm của Primitive Specialization:
Chỉ hỗ trợ một số kiểu dữ liệu cụ thể (
int,double,long).Không linh hoạt như các FI generic (
Predicate<T>,Function<T, R>, v.v.).
4. Ví dụ minh họa
Ví dụ 1: Sử dụng
PredicatevàFunctiontrong StreamList<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5); List<Integer> evenNumbers = numbers.stream() .filter(num -> num % 2 == 0) // Predicate .map(num -> num * 2) // Function .collect(Collectors.toList()); System.out.println(evenNumbers); // [4, 8]Ví dụ 2: Sử dụng
ConsumervàSupplierSupplier<String> messageSupplier = () -> "Hello, World!"; Consumer<String> messageConsumer = message -> System.out.println(message); messageConsumer.accept(messageSupplier.get()); // Hello, World!
Tổng kết
Các Functional Interface cơ bản như
Predicate,Function,Consumer, vàSupplierlà nền tảng quan trọng trong Java 8.Các FI chuyên biệt như
IntPredicate,BiFunction, v.v. giúp tối ưu hóa hiệu suất và hỗ trợ các tác vụ phức tạp hơn.Hiểu rõ các FI này giúp viết code ngắn gọn, hiệu quả và tận dụng tối đa sức mạnh của Stream API.
Bài tập thực hành
Tạo một
Predicate<String>để kiểm tra xem một chuỗi có độ dài lớn hơn 5 không.Sử dụng
Function<Integer, String>để chuyển đổi một số nguyên thành chuỗi có định dạng "Number: X".Viết một
Consumer<Integer>để in ra bình phương của một số.Sử dụng
Supplier<LocalDate>để cung cấp ngày hiện tại.
Gợi ý đáp án
Predicate:
Predicate<String> isLong = str -> str.length() > 5; System.out.println(isLong.test("Java")); // falseFunction:
Function<Integer, String> formatNumber = num -> "Number: " + num; System.out.println(formatNumber.apply(42)); // Number: 42Consumer:
Consumer<Integer> printSquare = num -> System.out.println(num * num); printSquare.accept(5); // 25Supplier:
Supplier<LocalDate> todaySupplier = () -> LocalDate.now(); System.out.println(todaySupplier.get()); // Ngày hiện tại
Kết luận
Bài viết này đã giới thiệu chi tiết về các Functional Interface phổ biến trong Java 8, từ cơ bản đến chuyên biệt. Hiểu rõ các FI này giúp bạn viết code hiệu quả hơn và tận dụng tối đa sức mạnh của Stream API trong các bài viết tiếp theo.