Chuyển tới nội dung chính

Functional Interfaces

Mục tiêu bài học

Sau bài này, bạn sẽ:

  • Hiểu được Functional Interface và annotation @FunctionalInterface
  • Sử dụng được các built-in functional interfaces: Predicate, Function, Consumer, Supplier
  • Nắm được cách compose functions với andThen, compose, and, or
  • Biết cách tạo custom functional interfaces
  • Phân biệt được các variants: UnaryOperator, BinaryOperator, Bi-variants

Bài trước: Lambda Expressions — Đã học cú pháp lambda và cách sử dụng. Bài này sẽ tìm hiểu các Functional Interfaces có sẵn trong Java và cách compose chúng.

Functional Interface là gì?

Giao diện hàm (Functional Interface) là một interface có đúng 1 abstract method — một phương thức trừu tượng duy nhất (SAM — Single Abstract Method). Nó là nền tảng cho lambda expressions trong Java.

// ✅ Functional Interface - có đúng 1 abstract method
@FunctionalInterface
public interface Calculator {
int calculate(int a, int b); // Duy nhất 1 abstract method
}

// Sử dụng với lambda
Calculator add = (a, b) -> a + b;
Calculator multiply = (a, b) -> a * b;

System.out.println(add.calculate(10, 5)); // 15
System.out.println(multiply.calculate(10, 5)); // 50
Đặc điểm
  • đúng 1 abstract method (không nhiều hơn, không ít hơn)
  • Có thể có nhiều default methods (từ Java 8)
  • Có thể có nhiều static methods
  • Có thể có methods từ Object class (equals, toString, hashCode)

Annotation @FunctionalInterface

@FunctionalInterface là một optional annotation để đánh dấu interface là functional interface.

// ✅ Với annotation - compiler kiểm tra
@FunctionalInterface
public interface Processor {
void process(String data);

// ❌ Thêm abstract method thứ 2 → compile error
// void validate(String data);

// ✅ Default methods - OK
default void log(String message) {
System.out.println("Log: " + message);
}

// ✅ Static methods - OK
static Processor createDefault() {
return data -> System.out.println("Processing: " + data);
}
}

Tại sao nên dùng @FunctionalInterface?

Lợi íchMô tả
Kiểm tra lúc biên dịchCompiler báo lỗi nếu có > 1 abstract method
Tài liệu hóaRõ ràng intent của interface
Hỗ trợ IDEIDE hỗ trợ auto-complete cho lambda
An toàn khi tái cấu trúcNgăn accidentally thêm abstract methods
Lưu ý

@FunctionalInterfaceoptional - interface vẫn là functional interface ngay cả khi không có annotation, miễn có đúng 1 abstract method.

// ✅ Vẫn là functional interface (không có annotation)
public interface MyFunction {
void execute();
}

// Vẫn dùng được với lambda
MyFunction func = () -> System.out.println("Executing");

Built-in Functional Interfaces

Java cung cấp sẵn nhiều functional interfaces trong package java.util.function. Đây là các interfaces phổ biến nhất:

1. Predicate<T> - Test một điều kiện

Method: boolean test(T t)

Dùng để kiểm tra điều kiện (trả về true/false).

import java.util.function.Predicate;
import java.util.Arrays;
import java.util.List;

public class PredicateExample {
public static void main(String[] args) {
// Predicate cơ bản
Predicate<Integer> isEven = n -> n % 2 == 0;
System.out.println(isEven.test(4)); // true
System.out.println(isEven.test(5)); // false

// Predicate với String
Predicate<String> isEmpty = String::isEmpty;
Predicate<String> isLong = s -> s.length() > 10;

// Sử dụng với filter
List<String> names = Arrays.asList("An", "Bình", "Cường", "Dung", "Em");
names.stream()
.filter(name -> name.length() > 2)
.forEach(System.out::println);
}
}

2. Function<T, R> - Transform dữ liệu

Method: R apply(T t)

Dùng để chuyển đổi từ type T sang type R.

import java.util.function.Function;

public class FunctionExample {
public static void main(String[] args) {
// String -> Integer: độ dài string
Function<String, Integer> stringLength = String::length;
System.out.println(stringLength.apply("Hello")); // 5

// Integer -> String: chuyển sang binary
Function<Integer, String> toBinary = Integer::toBinaryString;
System.out.println(toBinary.apply(10)); // "1010"

// String -> String: uppercase
Function<String, String> toUpper = String::toUpperCase;
System.out.println(toUpper.apply("hello")); // "HELLO"

// Sử dụng với map
List<String> words = Arrays.asList("java", "python", "c++");
words.stream()
.map(String::toUpperCase) // Function<String, String>
.forEach(System.out::println);
}
}

3. Consumer<T> - Thực hiện action

Method: void accept(T t)

Dùng để thực hiện một action với input, không trả về gì (void).

import java.util.function.Consumer;

public class ConsumerExample {
public static void main(String[] args) {
// Consumer cơ bản
Consumer<String> print = System.out::println;
print.accept("Hello Consumer");

// Consumer với logic phức tạp
Consumer<Person> printPerson = p -> {
System.out.println("Name: " + p.getName());
System.out.println("Age: " + p.getAge());
};

// Sử dụng với forEach
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
numbers.forEach(n -> System.out.println("Number: " + n));

// Consumer chain
Consumer<String> log = msg -> System.out.println("[LOG] " + msg);
Consumer<String> saveToFile = msg -> System.out.println("[SAVE] " + msg);
Consumer<String> sendEmail = msg -> System.out.println("[EMAIL] " + msg);

Consumer<String> allActions = log.andThen(saveToFile).andThen(sendEmail);
allActions.accept("Important message");
}
}

4. Supplier<T> - Cung cấp giá trị

Method: T get()

Dùng để cung cấp/tạo giá trị mà không cần input.

import java.util.function.Supplier;

public class SupplierExample {
public static void main(String[] args) {
// Supplier cơ bản
Supplier<Double> randomValue = Math::random;
System.out.println(randomValue.get());

// Supplier với LocalDateTime
Supplier<LocalDateTime> currentTime = LocalDateTime::now;
System.out.println(currentTime.get());

// Lazy evaluation với Supplier
Supplier<String> expensiveOperation = () -> {
System.out.println("Executing expensive operation...");
return "Result";
};

// Chưa execute
System.out.println("Before get()");

// Bây giờ mới execute
String result = expensiveOperation.get();

// Factory pattern với Supplier
Supplier<List<String>> listFactory = ArrayList::new;
List<String> list1 = listFactory.get();
List<String> list2 = listFactory.get(); // Tạo instance mới
}
}

5. UnaryOperator<T> - Transform cùng type

Method: T apply(T t) (extends Function<T, T>)

Dùng khi input và output cùng type.

import java.util.function.UnaryOperator;

public class UnaryOperatorExample {
public static void main(String[] args) {
// String -> String
UnaryOperator<String> toUpperCase = String::toUpperCase;
System.out.println(toUpperCase.apply("hello")); // "HELLO"

// Integer -> Integer
UnaryOperator<Integer> square = x -> x * x;
System.out.println(square.apply(5)); // 25

// List replacement
List<String> names = Arrays.asList("an", "binh", "cuong");
names.replaceAll(String::toUpperCase); // UnaryOperator<String>
System.out.println(names); // [AN, BINH, CUONG]
}
}

6. BinaryOperator<T> - Combine 2 values cùng type

Method: T apply(T t1, T t2) (extends BiFunction<T, T, T>)

Dùng để kết hợp 2 giá trị cùng type thành 1 giá trị.

import java.util.function.BinaryOperator;

public class BinaryOperatorExample {
public static void main(String[] args) {
// Integer operations
BinaryOperator<Integer> add = (a, b) -> a + b;
BinaryOperator<Integer> multiply = (a, b) -> a * b;
BinaryOperator<Integer> max = Integer::max;

System.out.println(add.apply(10, 5)); // 15
System.out.println(multiply.apply(10, 5)); // 50
System.out.println(max.apply(10, 5)); // 10

// String concatenation
BinaryOperator<String> concat = (s1, s2) -> s1 + " " + s2;
System.out.println(concat.apply("Hello", "World")); // "Hello World"

// Sử dụng với reduce
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
int sum = numbers.stream()
.reduce(0, (a, b) -> a + b); // BinaryOperator<Integer>
System.out.println("Sum: " + sum); // 15
}
}

7. Bi-variants: BiFunction, BiPredicate, BiConsumer

Các variants nhận 2 parameters.

import java.util.function.*;

public class BiVariantsExample {
public static void main(String[] args) {
// BiFunction<T, U, R>: (T, U) -> R
BiFunction<Integer, Integer, Integer> add = (a, b) -> a + b;
BiFunction<String, String, String> concat = (s1, s2) -> s1 + s2;
System.out.println(add.apply(10, 20)); // 30
System.out.println(concat.apply("Hello", "World")); // "HelloWorld"

// BiPredicate<T, U>: (T, U) -> boolean
BiPredicate<String, Integer> isLengthEqual = (s, len) -> s.length() == len;
System.out.println(isLengthEqual.test("Hello", 5)); // true

// BiConsumer<T, U>: (T, U) -> void
BiConsumer<String, Integer> printPair = (name, age) ->
System.out.println(name + " is " + age + " years old");
printPair.accept("An", 25);

// Map.forEach sử dụng BiConsumer
Map<String, Integer> scores = new HashMap<>();
scores.put("An", 85);
scores.put("Bình", 90);
scores.forEach((name, score) -> System.out.println(name + ": " + score));
}
}

Bảng tổng hợp Built-in Functional Interfaces

InterfaceMethodInput → OutputUse Case
Predicate<T>boolean test(T)T → booleanKiểm tra điều kiện, filter
Function<T,R>R apply(T)T → RTransform/map dữ liệu
Consumer<T>void accept(T)T → voidThực hiện action, side effects
Supplier<T>T get()() → TCung cấp/tạo giá trị, factory
UnaryOperator<T>T apply(T)T → TTransform cùng type
BinaryOperator<T>T apply(T, T)(T, T) → TCombine 2 values cùng type
BiFunction<T,U,R>R apply(T, U)(T, U) → RTransform 2 inputs
BiPredicate<T,U>boolean test(T, U)(T, U) → booleanTest 2 inputs
BiConsumer<T,U>void accept(T, U)(T, U) → voidAction trên 2 inputs

Biến thể cho kiểu nguyên thủy (Primitive Specializations)

Java cung cấp các biến thể chuyên biệt cho kiểu nguyên thủy (int, long, double) để tránh chi phí boxing/unboxing:

Functional InterfacePrimitive VariantsMethod
Predicate<T>IntPredicate, LongPredicate, DoublePredicateboolean test(int/long/double)
Function<T,R>IntFunction<R>, LongFunction<R>, DoubleFunction<R>R apply(int/long/double)
ToIntFunction<T>, ToLongFunction<T>, ToDoubleFunction<T>int/long/double applyAsInt/Long/Double(T)
IntToDoubleFunction, IntToLongFunction, LongToIntFunction...Chuyển đổi giữa các kiểu nguyên thủy
Consumer<T>IntConsumer, LongConsumer, DoubleConsumervoid accept(int/long/double)
Supplier<T>IntSupplier, LongSupplier, DoubleSupplier, BooleanSupplierint/long/double/boolean getAsInt/Long/Double/Boolean()
UnaryOperator<T>IntUnaryOperator, LongUnaryOperator, DoubleUnaryOperatorint/long/double applyAsInt/Long/Double(int/long/double)
BinaryOperator<T>IntBinaryOperator, LongBinaryOperator, DoubleBinaryOperatorint/long/double applyAsInt/Long/Double(int/long/double, int/long/double)

Mixed-type consumers cho object + primitive:

InterfaceMethodMô tả
ObjIntConsumer\<T\>void accept(T t, int value)Nhận 1 object + 1 int
ObjLongConsumer\<T\>void accept(T t, long value)Nhận 1 object + 1 long
ObjDoubleConsumer\<T\>void accept(T t, double value)Nhận 1 object + 1 double
import java.util.function.ObjIntConsumer;

// Ví dụ: in string với index
ObjIntConsumer<String> printWithIndex = (s, i) -> System.out.println(i + ": " + s);
printWithIndex.accept("Hello", 1); // 1: Hello
printWithIndex.accept("World", 2); // 2: World

// Use case: Map.forEach với primitive values
Map<String, Integer> scores = Map.of("An", 85, "Bình", 90);
ObjIntConsumer<String> printScore = (name, score) ->
System.out.println(name + " scored " + score);
// Note: scores.forEach nhận BiConsumer<String, Integer>, không phải ObjIntConsumer
// ✅ Dùng primitive variant — không boxing
IntPredicate isEven = n -> n % 2 == 0;
IntUnaryOperator doubleIt = n -> n * 2;
IntBinaryOperator add = (a, b) -> a + b;
ToIntFunction<String> stringLength = String::length;

// ❌ Dùng generic — phải boxing Integer ↔ int mỗi lần
Predicate<Integer> isEvenBoxed = n -> n % 2 == 0; // Tốn hơn!
OCP Exam Tips
  • Đề thi rất hay hỏi về primitive variants — nhớ tên method khác nhau: test(), applyAsInt(), getAsDouble()...
  • BooleanSupplier là biến thể duy nhất cho boolean — không có BooleanPredicate hay BooleanConsumer
  • IntFunction<R> nhận int trả về R, còn ToIntFunction<T> nhận T trả về int — dễ nhầm!

Composing Functions

Functional interfaces hỗ trợ compose (kết hợp) nhiều functions thành một.

1. Function Composition: andThen()compose()

import java.util.function.Function;

public class FunctionCompositionExample {
public static void main(String[] args) {
Function<Integer, Integer> multiplyBy2 = x -> x * 2;
Function<Integer, Integer> add3 = x -> x + 3;

// andThen: f.andThen(g) = g(f(x))
Function<Integer, Integer> multiplyThenAdd = multiplyBy2.andThen(add3);
System.out.println(multiplyThenAdd.apply(5)); // (5 * 2) + 3 = 13

// compose: f.compose(g) = f(g(x))
Function<Integer, Integer> addThenMultiply = multiplyBy2.compose(add3);
System.out.println(addThenMultiply.apply(5)); // (5 + 3) * 2 = 16

// Chain nhiều functions
Function<String, String> pipeline = ((Function<String, String>) String::trim)
.andThen(String::toLowerCase)
.andThen(s -> s.replace(" ", "_"));

System.out.println(pipeline.apply(" Hello World ")); // "hello_world"
}
}

So sánh andThen vs compose:

MethodOrderMô tảVí dụ với f=*2, g=+3
andThenf → gf.andThen(g) = g(f(x))(x*2)+3 = 13 với x=5
composeg → ff.compose(g) = f(g(x))(x+3)*2 = 16 với x=5

2. Predicate Composition: and(), or(), negate()

import java.util.function.Predicate;

public class PredicateCompositionExample {
public static void main(String[] args) {
Predicate<Integer> isEven = n -> n % 2 == 0;
Predicate<Integer> isPositive = n -> n > 0;
Predicate<Integer> isGreaterThan10 = n -> n > 10;

// and: cả 2 điều kiện đều true
Predicate<Integer> isEvenAndPositive = isEven.and(isPositive);
System.out.println(isEvenAndPositive.test(4)); // true
System.out.println(isEvenAndPositive.test(-4)); // false

// or: 1 trong 2 điều kiện true
Predicate<Integer> isEvenOrPositive = isEven.or(isPositive);
System.out.println(isEvenOrPositive.test(3)); // true (positive)
System.out.println(isEvenOrPositive.test(-4)); // true (even)
System.out.println(isEvenOrPositive.test(-3)); // false

// negate: đảo ngược điều kiện
Predicate<Integer> isOdd = isEven.negate();
System.out.println(isOdd.test(3)); // true
System.out.println(isOdd.test(4)); // false

// Chain nhiều predicates
Predicate<Integer> complex = isEven
.and(isPositive)
.and(isGreaterThan10);

System.out.println(complex.test(12)); // true
System.out.println(complex.test(8)); // false (not > 10)
System.out.println(complex.test(15)); // false (odd)
}
}

3. Consumer Composition: andThen()

import java.util.function.Consumer;

public class ConsumerCompositionExample {
public static void main(String[] args) {
Consumer<String> printToConsole = s -> System.out.println("Console: " + s);
Consumer<String> printToLog = s -> System.out.println("Log: " + s);
Consumer<String> printToFile = s -> System.out.println("File: " + s);

// andThen: thực hiện tuần tự
Consumer<String> printAll = printToConsole
.andThen(printToLog)
.andThen(printToFile);

printAll.accept("Important message");
// Output:
// Console: Important message
// Log: Important message
// File: Important message
}
}

4. Pipeline kết hợp — Ví dụ quy tắc kinh doanh

Composition thực sự mạnh mẽ khi kết hợp nhiều predicate thành quy tắc kinh doanh phức tạp:

// Quy tắc xét duyệt khoản vay
Predicate<LoanApplication> hasMinIncome = app -> app.getIncome() >= 10_000_000;
Predicate<LoanApplication> hasGoodCredit = app -> app.getCreditScore() >= 700;
Predicate<LoanApplication> isNotBlacklisted = app -> !app.isBlacklisted();
Predicate<LoanApplication> hasMinAge = app -> app.getAge() >= 21;

// Kết hợp: phải đủ thu nhập VÀ (tín dụng tốt HOẶC không trong danh sách đen) VÀ đủ tuổi
Predicate<LoanApplication> canApproveLoan = hasMinIncome
.and(hasGoodCredit.or(isNotBlacklisted))
.and(hasMinAge);

// Áp dụng
applications.stream()
.filter(canApproveLoan)
.forEach(app -> System.out.println("Approved: " + app.getName()));

// Đảo ngược: tìm các đơn bị từ chối
Predicate<LoanApplication> isRejected = canApproveLoan.negate();

Tạo Custom Functional Interface

// 1. Simple functional interface
@FunctionalInterface
public interface StringValidator {
boolean validate(String input);

// Default method
default StringValidator and(StringValidator other) {
return input -> this.validate(input) && other.validate(input);
}
}

// 2. Functional interface với generic
@FunctionalInterface
public interface Transformer<T, R> {
R transform(T input);

default <V> Transformer<T, V> andThen(Transformer<R, V> after) {
return input -> after.transform(this.transform(input));
}
}

// 3. Functional interface với multiple parameters
@FunctionalInterface
public interface TriFunction<T, U, V, R> {
R apply(T t, U u, V v);
}

// Sử dụng
public class CustomFunctionalInterfaceDemo {
public static void main(String[] args) {
// StringValidator
StringValidator notEmpty = s -> !s.isEmpty();
StringValidator minLength = s -> s.length() >= 5;
StringValidator hasDigit = s -> s.matches(".*\\d.*");

StringValidator fullValidator = notEmpty.and(minLength).and(hasDigit);
System.out.println(fullValidator.validate("Hello123")); // true
System.out.println(fullValidator.validate("Hi")); // false

// Transformer
Transformer<String, Integer> stringLength = String::length;
Transformer<Integer, String> intToString = Object::toString;

Transformer<String, String> pipeline = stringLength.andThen(intToString);
System.out.println(pipeline.transform("Hello")); // "5"

// TriFunction
TriFunction<Integer, Integer, Integer, Integer> sum3 = (a, b, c) -> a + b + c;
System.out.println(sum3.apply(1, 2, 3)); // 6
}
}

Currying — Chuyển đổi hàm nhiều tham số

Currying (đặt tên theo nhà toán học Haskell Curry) là kỹ thuật chuyển đổi một hàm nhận nhiều tham số thành chuỗi các hàm, mỗi hàm nhận một tham số.

import java.util.function.*;

public class CurryingExample {
public static void main(String[] args) {
// Hàm bình thường: nhận 2 tham số
BiFunction<Integer, Integer, Integer> add = (a, b) -> a + b;
System.out.println(add.apply(5, 3)); // 8

// Curried version: chuỗi các hàm, mỗi hàm nhận 1 tham số
Function<Integer, Function<Integer, Integer>> curriedAdd = a -> b -> a + b;
System.out.println(curriedAdd.apply(5).apply(3)); // 8

// Practical use: tạo specialized functions từ curried function
Function<Integer, Integer> add5 = curriedAdd.apply(5);
System.out.println(add5.apply(3)); // 8
System.out.println(add5.apply(10)); // 15
System.out.println(add5.apply(7)); // 12

// Currying với 3 tham số
Function<Integer, Function<Integer, Function<Integer, Integer>>> curriedSum3 =
a -> b -> c -> a + b + c;

Function<Integer, Function<Integer, Integer>> sum3with10 = curriedSum3.apply(10);
Function<Integer, Integer> sum3with10and20 = sum3with10.apply(20);
System.out.println(sum3with10and20.apply(5)); // 10 + 20 + 5 = 35

// Hoặc viết gọn:
System.out.println(curriedSum3.apply(10).apply(20).apply(5)); // 35
}
}
Lợi ích của Currying
  • Partial application: tạo các hàm chuyên biệt từ hàm tổng quát (ví dụ: add5 từ add)
  • Reusability: tái sử dụng logic với tham số cố định
  • Function composition: dễ dàng kết hợp các hàm curried
Lưu ý

Java không có built-in currying support như Scala hay Haskell. Currying trong Java là pattern thủ công — cú pháp có thể phức tạp với nhiều tham số (nhiều Function<> lồng nhau).

Các trường hợp đặc biệt

Function.identity() vs x -> x

Hai cách viết này tương đương về logic, nhưng khác về implementation:

Function<String, String> identity1 = Function.identity();
Function<String, String> identity2 = x -> x;

// identity1 == identity1 → true (cùng instance, được cache)
// identity2 == identity2 → false (lambda mới mỗi lần)

Function.identity() trả về cùng một instance mỗi lần gọi (singleton), trong khi x -> x có thể tạo instance mới. Trong thực tế, hiệu năng khác biệt không đáng kể, nhưng Function.identity() rõ ràng hơn về ý đồ.

@FunctionalInterface trên interface có default methods

@FunctionalInterface
interface MyInterface {
void doSomething(); // 1 abstract method → OK

default void helper() { } // Default method — không tính
default void helper2() { } // Thêm default method — vẫn OK

static void util() { } // Static method — không tính

boolean equals(Object o); // Method từ Object — không tính
String toString(); // Method từ Object — không tính
}
// ✅ Vẫn là functional interface — chỉ có 1 abstract method: doSomething()
OCP Trap

Interface kế thừa interface khác:

@FunctionalInterface
interface A {
void methodA();
}

@FunctionalInterface
interface B extends A {
// Kế thừa methodA() — KHÔNG thêm abstract method nào
default void methodB() { }
}
// ✅ B vẫn là functional interface (1 abstract method: methodA())

// ❌ Nhưng nếu B thêm abstract method:
interface C extends A {
void methodC(); // Thêm abstract method thứ 2
}
// ❌ C KHÔNG phải functional interface (2 abstract methods)

Ví dụ thực tế: Validation Pipeline

import java.util.function.*;
import java.util.*;

class User {
private String username;
private String email;
private int age;

public User(String username, String email, int age) {
this.username = username;
this.email = email;
this.age = age;
}

public String getUsername() { return username; }
public String getEmail() { return email; }
public int getAge() { return age; }

@Override
public String toString() {
return String.format("User{username='%s', email='%s', age=%d}", username, email, age);
}
}

public class ValidationPipeline {
// Validators
static Predicate<User> hasValidUsername = u ->
u.getUsername() != null && u.getUsername().length() >= 3;

static Predicate<User> hasValidEmail = u ->
u.getEmail() != null && u.getEmail().matches("^[A-Za-z0-9+_.-]+@(.+)$");

static Predicate<User> hasValidAge = u ->
u.getAge() >= 18 && u.getAge() <= 100;

// Combined validator
static Predicate<User> isValidUser = hasValidUsername
.and(hasValidEmail)
.and(hasValidAge);

// Transformers
static Function<User, User> normalizeUsername = u ->
new User(u.getUsername().toLowerCase(), u.getEmail(), u.getAge());

static Function<User, User> normalizeEmail = u ->
new User(u.getUsername(), u.getEmail().toLowerCase(), u.getAge());

static Function<User, User> normalizeUser = normalizeUsername.andThen(normalizeEmail);

public static void main(String[] args) {
List<User> users = Arrays.asList(
new User("JOHN", "[email protected]", 25),
new User("ab", "invalid-email", 30),
new User("Alice", "[email protected]", 17),
new User("BOB", "[email protected]", 35)
);

System.out.println("=== Valid and Normalized Users ===");
users.stream()
.filter(isValidUser) // Validation
.map(normalizeUser) // Transformation
.forEach(System.out::println);

// Output:
// User{username='john', email='[email protected]', age=25}
// User{username='bob', email='[email protected]', age=35}
}
}

Best Practices

✅ DO

// 1. Sử dụng built-in functional interfaces khi có thể
Function<String, Integer> length = String::length; // ✅
// Thay vì tạo custom interface riêng

// 2. Compose functions để tái sử dụng
Function<String, String> trimAndLower = String::trim
.andThen(String::toLowerCase);

// 3. Use primitive variants để avoid boxing
IntPredicate isEven = n -> n % 2 == 0; // ✅ Tốt hơn Predicate<Integer>

// 4. Meaningful names
Predicate<User> isAdult = user -> user.getAge() >= 18; // ✅ Clear

❌ DON'T

// 1. Không tạo custom interface khi đã có built-in
@FunctionalInterface
interface MyConverter<T, R> {
R convert(T input); // ❌ Giống Function<T, R>
}

// 2. Không overuse composition - có thể khó debug
Function<String, String> complex = f1.andThen(f2).andThen(f3)
.andThen(f4).andThen(f5); // ❌ Quá phức tạp

// 3. Không ignore exceptions trong lambda
Function<String, Integer> parse = s -> Integer.parseInt(s); // ❌ NumberFormatException?

Tóm tắt

  • Functional Interface có đúng 1 abstract method, dùng với lambda
  • @FunctionalInterface annotation giúp compile-time check
  • Built-in interfaces: Predicate, Function, Consumer, Supplier, UnaryOperator, BinaryOperator, Bi-variants
  • Composition: andThen(), compose(), and(), or(), negate()
  • Custom interfaces: tạo khi built-in không đủ (ví dụ: TriFunction)
  • Primitive variants: tránh boxing/unboxing cho performance

Bài tập

Bài 1: Sử dụng Built-in Functional Interfaces

Hoàn thành code sau:

// 1. Predicate: kiểm tra string có chứa số
Predicate<String> hasDigit = // TODO

// 2. Function: chuyển string sang số words
Function<String, Integer> wordCount = // TODO

// 3. Consumer: in ra với prefix "[INFO]"
Consumer<String> logger = // TODO

// 4. Supplier: tạo UUID random
Supplier<String> uuidGenerator = // TODO

Bài 2: Function Composition

Tạo transformation pipeline cho string processing:

// Input: "  HELLO WORLD  "
// Steps:
// 1. Trim
// 2. Lowercase
// 3. Replace spaces with underscore
// 4. Add prefix "processed_"
// Expected output: "processed_hello_world"

Function<String, String> pipeline = // TODO (use andThen)

Bài 3: Validation System

Tạo validation system cho Product:

class Product {
String name;
double price;
int stock;
}

// Tạo các validators và combine chúng
Predicate<Product> hasValidName = // name != null && length > 0
Predicate<Product> hasValidPrice = // price > 0
Predicate<Product> hasStock = // stock > 0

Predicate<Product> isValidProduct = // combine tất cả

// Test với products

Bài tiếp theo: Stream API cơ bản →

Đọc thêm