0
0
Lập trình
TT

Khám Phá Function<T, R> Trong Java - Nền Tảng Lập Trình Hướng Chức Năng

Đăng vào 2 ngày trước

• 4 phút đọc

Giới thiệu

Trong tập trước, chúng ta đã làm quen với Predicate<T>, giao diện chức năng cho logic boolean khai báo. Giờ đây, chúng ta sẽ bước vào bộ biến đổi dữ liệu: Function<T, R>.

Nếu Predicate trả lời câu hỏi “Cái này có hợp lệ không?”, thì Function trả lời “Làm thế nào tôi có thể biến đổi cái này thành thứ khác?”. Giao diện này là trung tâm của lập trình chức năng trong Java, hỗ trợ mapping dữ liệu, pipelines và các biến đổi kinh doanh.

Function là gì?

Định nghĩa rất đơn giản:

java Copy
@FunctionalInterface
public interface Function<T, R> {
    R apply(T t);

    // Phương thức kết hợp
    default <V> Function<V, R> compose(Function<? super V, ? extends T> before) { ... }
    default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) { ... }

    // Tiện ích
    static <T> Function<T, T> identity() { ... }
}
  • Đầu vào: một đối tượng kiểu T.
  • Đầu ra: một đối tượng kiểu R.
  • Mục đích: biến đổi đầu vào thành đầu ra.

Tại sao nên sử dụng Function?

Trước Java 8, việc biến đổi thường yêu cầu logic inline gắn liền với vòng lặp hoặc gọi dịch vụ:

java Copy
List<String> names = new ArrayList<>();
for (User user : users) {
    names.add(user.getName().toUpperCase());
}

Với Function, việc biến đổi trở nên đầu tiêncó thể kết hợp:

java Copy
Function<User, String> toUpperName = user -> user.getName().toUpperCase();

List<String> names = users.stream()
    .map(toUpperName)
    .toList();

Giờ đây, logic biến đổi trở nên rõ ràng, có thể tái sử dụng và kiểm tra.

Ví dụ thực tế

  1. Biến đổi cơ bản: Chuyển Integer thành String:
java Copy
Function<Integer, String> intToString = i -> "Số: " + i;

System.out.println(intToString.apply(10)); // Số: 10
  1. Mapping qua Collections

Với streams, map làm việc trực tiếp với Function:

java Copy
List<String> names = List.of("Hob", "André", "Borba");

Function<String, Integer> nameLength = String::length;

List<Integer> lengths = names.stream()
    .map(nameLength)
    .toList();

System.out.println(lengths); // [3, 5, 5]
  1. Kết hợp các Function

Sử dụng andThen hoặc compose để tạo pipelines:

java Copy
Function<Integer, Integer> multiplyBy2 = x -> x * 2;
Function<Integer, Integer> square = x -> x * x;

Function<Integer, Integer> pipeline = multiplyBy2.andThen(square);

System.out.println(pipeline.apply(3)); // (3 * 2) ^ 2 = 36
  • andThen: áp dụng đầu tiên, sau đó là cái tiếp theo.
  • compose: áp dụng theo thứ tự ngược lại.
  1. Sử dụng Identity

Đôi khi, chúng ta cần một hàm không thực hiện gì:

java Copy
Function<String, String> identity = Function.identity();

System.out.println(identity.apply("Xin chào")); // Xin chào

Hữu ích trong các pipelines tổng quát khi bạn không muốn thực hiện biến đổi.

Mô hình thực tế

1. Mapping DTO

Chuyển đổi các đối tượng miền thành Data Transfer Objects (DTOs).

java Copy
Function<User, UserDTO> toDTO = user -> new UserDTO(user.getId(), user.getName());

2. Pipelines trong xử lý dữ liệu

Liên kết nhiều biến đổi (ví dụ: hệ thống ETL, bộ xử lý stream).

Quy tắc cụ thể theo miền

Đại diện cho các phép tính hoặc chính sách một cách khai báo (giá -> giá * giảm giá).

Thực hành tốt nhất

  • Đặt tên cho Functions theo biến đổi: Tên tốt truyền đạt ý định: toDTO, toUpperName, calculateTax.
  • Tận dụng Composition: Xây dựng pipelines sử dụng andThencompose thay vì lồng ghép lambdas.
  • Ưu tiên Functions thuần túy: Giữ cho Function không có tác dụng phụ, điều này đảm bảo tính dự đoán và khả năng kiểm tra.

Những cạm bẫy phổ biến

  • Tác dụng phụ ẩn: Đừng ghi log, thay đổi trạng thái hoặc ném ngoại lệ không kiểm soát bên trong các function.
  • Pipelines quá phức tạp: Quá nhiều function kết hợp có thể làm mờ độ dễ đọc. Chia thành các function có tên trung gian.
  • Mismatches về kiểu: Nhớ rằng Function là tổng quát, các loại không khớp trong composition thường gây lỗi biên dịch.

Phân tích chức năng

Hãy nghĩ về Function như một bộ biến đổi trong một dây chuyền sản xuất:

  • Nó nhận nguyên liệu thô (T).
  • Biến đổi nó thành sản phẩm hoàn chỉnh (R).
  • Có thể được liên kết với các bộ biến đổi khác để xây dựng một pipeline.

Kết luận

Functionxương sống của việc biến đổi dữ liệu trong lập trình chức năng Java. Bằng cách tách biệt logic biến đổi, bạn có thể xây dựng pipelines khai báo, quy tắc kinh doanh sạch và mapping có thể tái sử dụng.

Nó nâng cao Java từ các vòng lặp mệnh lệnh đến workflows chức năng, có thể kết hợp.

Điều gì tiếp theo

Trong tập tiếp theo, chúng ta sẽ khám phá Consumer, giao diện cho các hành động không có giá trị trả về. Nếu Predicate xác thực và Function biến đổi, Consumer thực thi.

Hãy theo dõi, bộ công cụ FP chỉ mới bắt đầu! 🚀

Gợi ý câu hỏi phỏng vấn
Không có dữ liệu

Không có dữ liệu

Bài viết được đề xuất
Bài viết cùng tác giả

Bình luận

Chưa có bình luận nào

Chưa có bình luận nào