Lambda & functional interface: lọc đơn hàng mà không cần anonymous class

4 câu hỏi~25 phútTừ khóa: lambda, functional interface, Predicate, Function, Supplier, Consumer, BiFunction, method reference, effectively final, anonymous class, this, invokedynamic

Mình muốn học Java 8.

Được. Học Java 8 để làm backend thì thứ tự nên là: Lambda → Functional Interface → Method Reference → Stream API → Optional. Lambda là nền, Stream ở bài 1.12 xây trên nó, Optional ở bài 1.13. Bài này dùng tiếp shop-backend: một service cần lọc danh sách Order theo nhiều điều kiện khác nhau.

1. Bài toán: mỗi điều kiện lọc là một method

public record Order(String id, String customerId, OrderStatus status, BigDecimal total) {}

class OrderReportService {
    List<Order> paidOrders(List<Order> orders) {
        List<Order> result = new ArrayList<>();
        for (Order o : orders) if (o.status() == OrderStatus.PAID) result.add(o);
        return result;
    }
    List<Order> bigOrders(List<Order> orders) {          // copy y hệt, chỉ khác điều kiện
        List<Order> result = new ArrayList<>();
        for (Order o : orders) if (o.total().compareTo(BigDecimal.valueOf(1_000_000)) > 0) result.add(o);
        return result;
    }
}

2. Vấn đề

List<Order> filter(List<Order> orders, OrderFilter f) { ... }

filter(orders, new OrderFilter() {
    @Override
    public boolean accept(Order o) {
        return o.status() == OrderStatus.PAID;
    }
});

3. Giải pháp: lambda + functional interface

4. Code từng bước

Bước 1: Một method lọc chung, nhận Predicate

static List<Order> filter(List<Order> orders, Predicate<Order> condition) {
    List<Order> result = new ArrayList<>();
    for (Order o : orders) if (condition.test(o)) result.add(o);
    return result;
}

Bước 2: Truyền điều kiện bằng lambda

List<Order> paid = filter(orders, o -> o.status() == OrderStatus.PAID);
List<Order> big  = filter(orders, o -> o.total().compareTo(BigDecimal.valueOf(1_000_000)) > 0);

// Predicate có default method để ghép điều kiện
Predicate<Order> isPaid = o -> o.status() == OrderStatus.PAID;
Predicate<Order> isBig  = o -> o.total().compareTo(BigDecimal.valueOf(1_000_000)) > 0;
List<Order> paidAndBig = filter(orders, isPaid.and(isBig));
List<Order> unpaid     = filter(orders, isPaid.negate());

Bước 3: Các functional interface còn lại trên cùng project

Function<Order, String>              toCustomer = o -> o.customerId();          // T → R
Consumer<Order>                      audit      = o -> log.info("Order {}", o.id()); // T → void
Supplier<String>                     newId      = () -> UUID.randomUUID().toString(); // () → T
BiFunction<BigDecimal, BigDecimal, BigDecimal> add = (a, b) -> a.add(b);       // (T, U) → R
BinaryOperator<BigDecimal>           add2       = BigDecimal::add;            // (T, T) → T
UnaryOperator<String>                upper      = String::toUpperCase;        // T → T

Bước 4: Method reference: lambda chỉ gọi đúng một method có sẵn

o -> o.customerId()             ≡  Order::customerId          // instance method của tham số
s -> Integer.parseInt(s)        ≡  Integer::parseInt          // static method
o -> auditService.record(o)     ≡  auditService::record       // method của một object cụ thể
() -> new ArrayList<Order>()    ≡  ArrayList::new             // constructor

5. Cách hoạt động

filter(orders, o -> o.status() == PAID)
                └──────┬──────────────┘
   compiler nhìn tham số: Predicate<Order>  (target type)
                       │  abstract method duy nhất: boolean test(Order)
                       ▼
   lambda = implementation của test()
                       │  bytecode: invokedynamic → LambdaMetafactory
                       ▼  (không sinh file OrderService$1.class như anonymous class)
   một object implement Predicate<Order>
                       │
   filter() gọi condition.test(o) cho từng order

6. So sánh

InterfaceMethodDạngVí dụ shop-backend
Predicate<T>testT → booleanđơn đã thanh toán?
Function<T,R>applyT → ROrder → customerId, Entity → DTO
Consumer<T>acceptT → voidghi log, gửi email
Supplier<T>get() → Tsinh id, giá trị mặc định lazy
BiFunction<T,U,R>apply(T, U) → Rcộng tiền, merge hai map
Runnable / Callable<V>run / call() → void / Vtask cho thread pool
Comparator<T>compare(T, T) → intsắp xếp đơn theo total

Có bản primitive để tránh boxing: IntPredicate, ToLongFunction, IntSupplier...

7. Khi nào nên dùng / không nên

Hai câu hỏi:

  1. Bạn nói lambda thường dùng với functional interface, vậy còn trường hợp nào khác không?
  2. Trả lời luôn câu bạn hỏi lúc nãy: lambda khác anonymous class thế nào?

Câu "thường dùng" là chưa chính xác, cần chỉnh lại: lambda bắt buộc phải có target type là một functional interface. Không có trường hợp lambda implement class hay interface có hai abstract method.

Predicate<Order> p = o -> o.total().signum() > 0;   // ✅ target type = Predicate
var p = o -> o.total().signum() > 0;                 // ❌ không có target type
Object p = () -> {};                                 // ❌ Object không phải functional interface

abstract class Notifier { abstract void send(Order o); }
Notifier n = o -> {};                                // ❌ lambda không extend class được

Lambda khác anonymous class thế nào? Nhìn thì giống viết tắt, nhưng semantics khác:

LambdaAnonymous class
TargetChỉ functional interfaceInterface bất kỳ hoặc extend class
thisLà object bao ngoài (không có scope riêng)Là chính object anonymous
StateKhông khai báo fieldCó field, nhiều method
Bytecodeinvokedynamic, tạo lúc runtimeSinh file Outer$1.class
Shadow biếnKhông được đặt tên tham số trùng biến localĐược (scope mới)
public class OrderReportService {
    public void demo() {
        Runnable a = new Runnable() {
            public void run() { System.out.println(this); }   // OrderReportService$1@...
        };
        Runnable b = () -> System.out.println(this);          // OrderReportService@...
        a.run(); b.run();
    }
}
OrderReportService object  ◄── this trong lambda
   └── anonymous Runnable  ◄── this trong anonymous class

Vì vậy câu "lambda là syntactic sugar của anonymous class" không nên trả lời "có" trọn vẹn: cú pháp thay thế được trong nhiều trường hợp, nhưng this, scope và cách compile khác nhau.

Mình viết threshold = threshold.add(x) rồi dùng trong lambda thì bị lỗi "must be final or effectively final". Sao vậy?

Đúng, đó là luật effectively final: lambda (và anonymous class từ Java 8) chỉ được đọc biến local mà không bị gán lại sau khi khởi tạo, dù không ghi chữ final.

BigDecimal threshold = BigDecimal.valueOf(1_000_000);
filter(orders, o -> o.total().compareTo(threshold) > 0);   // ✅ effectively final

BigDecimal threshold = BigDecimal.valueOf(1_000_000);
threshold = threshold.add(BigDecimal.TEN);                  // gán lại
filter(orders, o -> o.total().compareTo(threshold) > 0);   // ❌ compile error

BigDecimal sum = BigDecimal.ZERO;
orders.forEach(o -> sum = sum.add(o.total()));             // ❌ không cộng dồn kiểu này được

Lý do: lambda capture giá trị (copy) của biến local, và lambda có thể chạy sau khi method đã return hoặc ở thread khác. Nếu cho gán lại, bản copy và biến gốc lệch nhau, còn đa thread thì race condition. Field của object thì không bị luật này (vì đọc qua this), nhưng sửa field trong lambda chạy song song vẫn không an toàn.

Cách đúng để cộng doanh thu là không mutate gì cả: orders.stream().map(Order::total).reduce(BigDecimal.ZERO, BigDecimal::add), chính là bài 1.12.

Phỏng vấn hay hỏi "lambda là gì" hoặc "em biết gì về lambda". Trả lời phần lý thuyết thế nào?

Đừng lao vào code ngay. Đi theo: định nghĩa → mục đích → target type → ví dụ → lưu ý. (Câu "Java 8 có functional interface nào" đã có ở bài 1.2; ở đây đào sâu hơn.)

Câu 1: Lambda là gì? Em biết gì về lambda?

🇻🇳 Tiếng Việt:

Lambda là tính năng từ Java 8, cho phép viết ngắn gọn implementation của một functional interface, tức interface có đúng một abstract method, mà không cần anonymous class. Cú pháp là (params) -> expression. Lambda không có kiểu riêng, compiler suy ra target type từ ngữ cảnh. Mục đích là truyền hành vi như dữ liệu, giúp code gọn, nhất là với Stream API. Ví dụ trong dự án em lọc đơn đã thanh toán bằng orders.stream().filter(o -> o.status() == PAID). Java có sẵn Predicate, Function, Consumer, Supplier. Lambda chỉ dùng được biến local effectively final.

🇬🇧 English:

A lambda, introduced in Java 8, is a concise way to implement a functional interface, an interface with exactly one abstract method, without writing an anonymous class. The syntax is (params) -> expression. A lambda has no type of its own; the compiler infers its target type from the context. The point is to pass behavior as data, which keeps code short, especially with the Stream API. In my project I filter paid orders with orders.stream().filter(o -> o.status() == PAID). The JDK provides Predicate, Function, Consumer and Supplier. A lambda can only capture effectively final local variables.

Câu 2: Lambda có phải syntactic sugar của anonymous class không?

🇻🇳 Tiếng Việt:

Không hoàn toàn. Về cú pháp nó thay được anonymous class khi target là functional interface, nhưng semantics khác: lambda không tạo scope this riêng, this là object bao ngoài; lambda không có field và không extend class được. Về bytecode, lambda dùng invokedynamic và tạo implementation lúc runtime, không sinh file $1.class.

🇬🇧 English:

Not exactly. Syntactically it replaces an anonymous class when the target is a functional interface, but the semantics differ: a lambda has no this of its own, this refers to the enclosing instance; it cannot have fields or extend a class. At the bytecode level it uses invokedynamic and the implementation is created at runtime instead of a separate $1.class file.

Câu 3: Effectively final là gì, vì sao lambda cần nó?

🇻🇳 Tiếng Việt:

Là biến local không bị gán lại sau khi khởi tạo dù không ghi final. Lambda capture bản copy giá trị và có thể chạy sau hoặc ở thread khác, nên Java cấm gán lại để tránh lệch giá trị và race condition. Muốn cộng dồn thì dùng reduce hoặc collect thay vì mutate biến ngoài.

🇬🇧 English:

It's a local variable that is never reassigned after initialization, even without the final keyword. A lambda captures a copy of the value and may run later or on another thread, so Java forbids reassignment to avoid stale values and race conditions. To accumulate, use reduce or collect instead of mutating an outer variable.

Câu 4: Method reference là gì?

🇻🇳 Tiếng Việt:

Là cách viết ngắn hơn của lambda khi lambda chỉ gọi đúng một method có sẵn, ví dụ Order::customerId thay cho o -> o.customerId(). Có bốn dạng: static method, instance method của tham số, instance method của object cụ thể, và constructor ArrayList::new.

🇬🇧 English:

It's a shorter form of a lambda that just calls one existing method, e.g. Order::customerId instead of o -> o.customerId(). There are four kinds: static method, instance method of the parameter, instance method of a specific object, and constructor references like ArrayList::new.

Cách nhớ ngắn gọn


Tự kiểm tra

var f = x -> x * 2; có compile không?

Không. Lambda cần target type là functional interface; var không cho compiler biết interface nào.

Interface có 1 abstract method và 3 default method có phải functional interface không?

Có. Chỉ đếm abstract method; default, static và method public của Object không tính.

In this trong lambda bên trong method của OrderReportService ra gì?

Instance OrderReportService bao ngoài, vì lambda không tạo scope this riêng.

Muốn tạo id mới chỉ khi cần (lazy) thì dùng interface nào?

Supplier<String>: () -> UUID.randomUUID().toString(), chỉ chạy khi gọi get().

Viết lại o -> o.total() bằng method reference.

Order::total.