Lambda Expressions
Sau bài này, bạn sẽ:
- Hiểu được Lambda Expression là gì và lợi ích của nó
- Nắm được cú pháp lambda: parameters, arrow operator, body
- Phân biệt được expression body và block body
- Hiểu được variable capture và effectively final
- So sánh được lambda với anonymous inner class
Bài trước: Giới thiệu Functional Programming — Đã tìm hiểu về FP, Pure Functions và First-Class Functions. Bài này sẽ học cách viết anonymous functions ngắn gọn với Lambda Expressions.
Lambda Expression là gì?
Lambda expression là một hàm vô danh (anonymous function) - một function không có tên, có thể được truyền như một argument hoặc gán cho một biến.
Lambda được giới thiệu từ Java 8 (2014) để viết code functional programming ngắn gọn hơn.
// Trước Java 8: Anonymous inner class
Runnable oldWay = new Runnable() {
@Override
public void run() {
System.out.println("Hello World");
}
};
// Java 8+: Lambda expression
Runnable newWay = () -> System.out.println("Hello World");
- Ngắn gọn: Giảm boilerplate code
- Dễ đọc: Tập trung vào logic, không phải cú pháp
- Functional programming: Enable Stream API và functional style
- Type inference: Compiler tự suy luận kiểu dữ liệu
Cú pháp Lambda Expression
Cú pháp cơ bản
(parameters) -> expression
hoặc
(parameters) -> { statements; }
Các thành phần
| Thành phần | Mô tả | Ví dụ |
|---|---|---|
| Parameters | Danh sách tham số (có thể rỗng) | (x, y), (String s), () |
| Arrow operator | Toán tử mũi tên -> | -> |
| Body | Expression hoặc block of statements | x * x, { return x * x; } |
Các dạng viết Lambda
1. No parameters (Không tham số)
// Runnable: void run()
Runnable r = () -> System.out.println("No parameters");
// Supplier<T>: T get()
Supplier<Double> randomValue = () -> Math.random();
System.out.println(randomValue.get());
2. Single parameter (Một tham số)
// ✅ Có thể bỏ ngoặc đơn
Consumer<String> print = message -> System.out.println(message);
// Cũng hợp lệ, nhưng redundant
Consumer<String> printVerbose = (message) -> System.out.println(message);
// Function<T, R>: R apply(T)
Function<Integer, Integer> square = x -> x * x;
System.out.println(square.apply(5)); // 25
Khi chỉ có 1 parameter, nên bỏ ngoặc đơn để code ngắn gọn hơn:
list.forEach(item -> System.out.println(item)); // ✅ Recommended
list.forEach((item) -> System.out.println(item)); // ⚠️ Verbose
3. Multiple parameters (Nhiều tham số)
// BiFunction<T, U, R>: R apply(T, U)
BiFunction<Integer, Integer, Integer> add = (a, b) -> a + b;
System.out.println(add.apply(10, 20)); // 30
// Comparator<T>: int compare(T, T)
Comparator<String> lengthComparator = (s1, s2) -> s1.length() - s2.length();
// BinaryOperator<T>: T apply(T, T)
BinaryOperator<Integer> multiply = (x, y) -> x * y;
Với nhiều parameters, bắt buộc phải có ngoặc đơn:
BiFunction<Integer, Integer, Integer> add = (a, b) -> a + b; // ✅
BiFunction<Integer, Integer, Integer> add = a, b -> a + b; // ❌ Compile error
4. Explicit type declarations (Khai báo kiểu rõ ràng)
// Type inference (compiler tự suy luận)
BiFunction<Integer, Integer, Integer> add1 = (a, b) -> a + b;
// Explicit types (khai báo rõ ràng)
BiFunction<Integer, Integer, Integer> add2 = (Integer a, Integer b) -> a + b;
- Thường không cần: Compiler thường suy luận đúng
- Cần khi: Code phức tạp, nhiều overloaded methods, hoặc muốn tăng readability
Lambda Body: Expression vs Block
Thân biểu thức (Expression Body) - Single Expression
// ✅ Single expression - không cần return, không cần dấu { }
Function<Integer, Integer> square = x -> x * x;
Predicate<String> isEmpty = s -> s.isEmpty();
BiFunction<Integer, Integer, Integer> max = (a, b) -> a > b ? a : b;
Thân khối lệnh (Block Body) - Multiple Statements
// ✅ Multiple statements - cần { }, cần return nếu có giá trị trả về
Function<Integer, Integer> processNumber = x -> {
System.out.println("Processing: " + x);
int result = x * x;
System.out.println("Result: " + result);
return result; // Bắt buộc phải có return
};
// Consumer with block
Consumer<List<String>> printList = list -> {
System.out.println("List size: " + list.size());
for (String item : list) {
System.out.println("- " + item);
}
// Không cần return vì Consumer trả về void
};
// ❌ Block body nhưng quên return
Function<Integer, Integer> wrong = x -> {
x * x; // ❌ Compile error: missing return statement
};
// ✅ Phải có return
Function<Integer, Integer> correct = x -> {
return x * x;
};
// ✅ Hoặc dùng expression body
Function<Integer, Integer> better = x -> x * x;
Bắt giữ biến (Variable Capture) - Effectively Final
Lambda có thể sử dụng các biến từ scope bên ngoài, nhưng biến đó phải là effectively final (không thay đổi giá trị sau khi khởi tạo).
public class VariableCaptureDemo {
public static void main(String[] args) {
int factor = 10; // Effectively final
// ✅ Lambda capture 'factor' - OK
Function<Integer, Integer> multiplier = x -> x * factor;
System.out.println(multiplier.apply(5)); // 50
// ❌ Nếu thay đổi 'factor', compile error
// factor = 20; // ❌ Error: variable used in lambda should be final or effectively final
// ✅ Khai báo final explicitly
final int divisor = 2;
Function<Integer, Integer> divider = x -> x / divisor;
}
public void captureInstanceVariable() {
// ✅ Instance variables có thể thay đổi (không phải effectively final requirement)
List<String> list = new ArrayList<>();
Runnable r = () -> {
list.add("Item"); // ✅ OK - list reference không đổi
// list = new ArrayList<>(); // ❌ Không thể gán lại reference
};
}
}
Tại sao phải Effectively Final?
| Lý do | Giải thích |
|---|---|
| An toàn luồng (Thread safety) | Lambda có thể chạy ở thread khác, nếu biến thay đổi → race condition |
| Tính nhất quán của bao đóng (Closure) | Lambda capture snapshot của biến, không phải reference |
| Compiler optimization | Giúp compiler optimize lambda execution |
Effectively final theo JLS §4.12.4
Theo Java Language Specification, một biến là effectively final nếu nó không bao giờ được gán lại sau khi khởi tạo — dù không có từ khóa final tường minh.
int x = 10; // effectively final — không bao giờ gán lại
final int y = 20; // explicitly final
int z = 30;
z = 40; // z KHÔNG phải effectively final — đã gán lại
// Lambda chỉ có thể dùng x và y, không dùng được z
Runnable r = () -> System.out.println(x + y);
Thiết kế này không phải ngẫu nhiên. Lambda trong Java bắt giữ giá trị (capture by value) của biến, không phải tham chiếu đến biến. Nếu biến thay đổi sau khi lambda được tạo, giá trị trong lambda sẽ "cũ" — gây ra bug khó tìm. Quy tắc effectively final ngăn chặn vấn đề này ngay từ lúc biên dịch.
Dùng wrapper object hoặc array:
// ✅ Dùng array (reference không đổi, value thay đổi)
int[] counter = {0};
list.forEach(item -> counter[0]++);
// ✅ Dùng wrapper class
AtomicInteger atomicCounter = new AtomicInteger(0);
list.forEach(item -> atomicCounter.incrementAndGet());
Trường hợp đặc biệt: Mutable array/object trick
Kỹ thuật dùng array int[] counter = {0} hoạt động vì reference của array là effectively final, dù nội dung thay đổi:
List<String> list = Arrays.asList("A", "B", "C");
// ✅ Compile OK - reference 'counter' không đổi
int[] counter = {0};
list.forEach(item -> counter[0]++);
System.out.println("Count: " + counter[0]); // 3
LƯU Ý QUAN TRỌNG: Kỹ thuật này KHÔNG thread-safe! Nếu nhiều threads cùng modify counter[0], sẽ xảy ra race condition:
// ❌ KHÔNG an toàn với multi-threading
int[] counter = {0};
list.parallelStream().forEach(item -> counter[0]++); // Race condition!
// ✅ Thread-safe alternative: dùng AtomicInteger
AtomicInteger safeCounter = new AtomicInteger(0);
list.parallelStream().forEach(item -> safeCounter.incrementAndGet()); // Safe
Best practice: Nếu cần mutable state trong lambda:
- Single thread: Array trick OK (nhưng code smell - nên refactor)
- Multi-thread: PHẢI dùng
AtomicInteger,AtomicReference, hoặc synchronization - Tốt nhất: Tránh side effects, dùng functional approach (reduce, collect)
Lambda Scope: this keyword
Trong lambda, từ khóa this refer đến enclosing class instance, không phải lambda instance (khác với anonymous class).
public class LambdaScopeDemo {
private String name = "Outer";
public void testLambdaScope() {
// Lambda: 'this' refers to LambdaScopeDemo instance
Runnable lambdaRunnable = () -> {
System.out.println("Lambda this: " + this.name); // "Outer"
System.out.println("Lambda this class: " + this.getClass().getName());
// LambdaScopeDemo
};
// Anonymous class: 'this' refers to anonymous class instance
Runnable anonymousRunnable = new Runnable() {
private String name = "Inner";
@Override
public void run() {
System.out.println("Anonymous this: " + this.name); // "Inner"
System.out.println("Anonymous this class: " + this.getClass().getName());
// LambdaScopeDemo$1
System.out.println("Outer this: " + LambdaScopeDemo.this.name); // "Outer"
}
};
lambdaRunnable.run();
anonymousRunnable.run();
}
public static void main(String[] args) {
new LambdaScopeDemo().testLambdaScope();
}
}
Output:
Lambda this: Outer
Lambda this class: com.example.LambdaScopeDemo
Anonymous this: Inner
Anonymous this class: com.example.LambdaScopeDemo$1
Outer this: Outer
Lambda vs Anonymous Inner Class
So sánh chi tiết
| Khía cạnh | Lambda Expression | Anonymous Inner Class |
|---|---|---|
| Cú pháp | Ngắn gọn: x -> x * 2 | Dài dòng: new Interface() { ... } |
| Functional interface | Chỉ dùng được với functional interface | Dùng với bất kỳ interface/class nào |
this keyword | Refers to enclosing class | Refers to anonymous class |
| Variable capture | Chỉ effectively final | Chỉ effectively final |
| Compile | Không tạo .class file riêng | Tạo OuterClass$1.class file |
| Performance | Nhanh hơn (invokedynamic) | Chậm hơn (tạo object mỗi lần) |
| Scope | Không có scope riêng | Có scope riêng (có thể khai báo variables) |
| Access modifiers | Không có | Có thể là final, static, etc. |
Ví dụ so sánh
import java.util.*;
public class LambdaVsAnonymousClass {
public static void main(String[] args) {
List<String> list = Arrays.asList("Java", "Python", "C++", "JavaScript");
// ❌ Anonymous Inner Class - dài dòng
Collections.sort(list, new Comparator<String>() {
@Override
public int compare(String s1, String s2) {
return s1.length() - s2.length();
}
});
// ✅ Lambda Expression - ngắn gọn
Collections.sort(list, (s1, s2) -> s1.length() - s2.length());
// ✅ Method Reference - ngắn gọn nhất
Collections.sort(list, Comparator.comparingInt(String::length));
}
}
Bên trong hoạt động ra sao: invokedynamic
Nhiều người nghĩ lambda chỉ là "đường cú pháp" (syntactic sugar) cho lớp vô danh (anonymous class). Thực tế, JVM xử lý lambda hoàn toàn khác — hiệu quả hơn nhiều.
Lambda KHÔNG tạo class file
Khi bạn viết anonymous class, compiler tạo một file .class riêng (ví dụ MyClass$1.class). Nhưng với lambda, không có file .class nào được tạo tại compile time.
Thay vào đó, compiler tạo một lệnh invokedynamic trong bytecode. Tại runtime, JVM sử dụng LambdaMetafactory để tạo implementation cho lambda — một cách linh hoạt và tối ưu hơn.
// Bytecode pseudocode cho lambda
// Thay vì: new MyClass$1() (anonymous class)
// Lambda dùng: invokedynamic → LambdaMetafactory.metafactory(...)
Tại sao invokedynamic nhanh hơn?
| Khía cạnh | Anonymous Class | Lambda (invokedynamic) |
|---|---|---|
| Class loading | Tạo và load class mới mỗi lần | Không tạo class file |
| Memory | Object mới mỗi lần gọi | JVM có thể cache và tái sử dụng |
| Khởi động | Chậm (class loading overhead) | Nhanh (bootstrap một lần) |
| Tối ưu hóa | Hạn chế | JVM tự do chọn strategy tối ưu nhất |
Đọc thêm: JEP 276 — Dynamic Linking of Language-Defined Object Models và bài trình bày của Brian Goetz "Lambda: A Peek Under the Hood" tại JavaOne 2013.
Các trường hợp đặc biệt hay gặp trong OCP
Lambda và Checked Exceptions
Lambda trong Runnable không thể ném checked exception, nhưng Callable thì có thể:
// ❌ Compile error — Runnable.run() không throws Exception
Runnable r = () -> {
throw new IOException("error"); // ❌ Unhandled exception
};
// ✅ Callable.call() throws Exception
Callable<String> c = () -> {
throw new IOException("error"); // ✅ OK
return "result";
};
Vấn đề thực tế: Stream API và các functional interfaces chuẩn (Function, Consumer, v.v.) không declare checked exceptions, nhưng code thực tế thường gặp I/O operations:
List<Path> paths = List.of(Path.of("file1.txt"), Path.of("file2.txt"));
// ❌ KHÔNG compile — Files.readString() throws IOException
// paths.stream().map(path -> Files.readString(path));
Giải pháp 1: Try-catch trong lambda
// ✅ Wrap exception trong lambda body
List<String> contents = paths.stream()
.map(path -> {
try {
return Files.readString(path);
} catch (IOException e) {
throw new UncheckedIOException(e); // Wrap as unchecked
}
})
.toList();
Nhược điểm: Boilerplate code lặp lại nhiều lần, giảm tính concise của lambda.
Giải pháp 2: ThrowingFunction wrapper (Advanced)
Tạo functional interface cho phép throws exception, kết hợp với wrapper method:
@FunctionalInterface
interface ThrowingFunction<T, R> {
R apply(T t) throws Exception;
}
static <T, R> Function<T, R> unchecked(ThrowingFunction<T, R> f) {
return t -> {
try {
return f.apply(t);
} catch (Exception e) {
throw new RuntimeException(e); // Hoặc UncheckedIOException
}
};
}
Sử dụng:
import static YourClass.unchecked; // Static import
// ✅ Code ngắn gọn, dễ đọc
List<String> contents = paths.stream()
.map(unchecked(path -> Files.readString(path)))
.toList();
// Hoặc với method reference
List<String> contents = paths.stream()
.map(unchecked(Files::readString))
.toList();
Thư viện bên ngoài: Nhiều libraries cung cấp utilities tương tự:
- Vavr:
io.vavr.CheckedFunction1 - jOOλ:
org.jooq.lambda.Unchecked
Xem thêm: Modern Java in Action (Ch.3) — Functional-style error handling.
var trong tham số lambda (Java 11+)
Từ Java 11, bạn có thể dùng var cho tham số lambda — hữu ích khi cần annotation:
// Không có var — không thể thêm annotation
BiFunction<String, String, String> f1 = (a, b) -> a + b;
// Có var — có thể thêm annotation
BiFunction<String, String, String> f2 = (@NonNull var a, @NonNull var b) -> a + b;
// ⚠️ Phải dùng var cho TẤT CẢ tham số, không được trộn
// ❌ (var a, String b) -> a + b // Compile error
Lỗi suy luận kiểu khi overload
public class OverloadDemo {
static void execute(Runnable r) { r.run(); }
static void execute(Callable<Integer> c) throws Exception { c.call(); }
public static void main(String[] args) {
// ❌ Ambiguous — compiler không biết chọn overload nào
// execute(() -> System.out.println("hello"));
// ✅ Cast tường minh
execute((Runnable) () -> System.out.println("hello"));
}
}
Ví dụ thực tế
1. Sorting với Comparator
import java.util.*;
class Employee {
private String name;
private int age;
private double salary;
public Employee(String name, int age, double salary) {
this.name = name;
this.age = age;
this.salary = salary;
}
// Getters
public String getName() { return name; }
public int getAge() { return age; }
public double getSalary() { return salary; }
@Override
public String toString() {
return String.format("%s (%d tuổi, %.0f VND)", name, age, salary);
}
}
public class SortingExample {
public static void main(String[] args) {
List<Employee> employees = Arrays.asList(
new Employee("An", 30, 15000000),
new Employee("Bình", 25, 12000000),
new Employee("Cường", 35, 20000000)
);
// Sort by age
employees.sort((e1, e2) -> e1.getAge() - e2.getAge());
System.out.println("Sorted by age: " + employees);
// Sort by salary descending
employees.sort((e1, e2) -> Double.compare(e2.getSalary(), e1.getSalary()));
System.out.println("Sorted by salary desc: " + employees);
// Sort by name length
employees.sort(Comparator.comparingInt(e -> e.getName().length()));
System.out.println("Sorted by name length: " + employees);
}
}
2. Event Handling (Swing/JavaFX)
import javax.swing.*;
public class EventHandlingExample {
public static void main(String[] args) {
JFrame frame = new JFrame("Lambda Event Demo");
JButton button = new JButton("Click Me");
// ❌ Old way: Anonymous inner class
button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("Button clicked!");
}
});
// ✅ Lambda way
button.addActionListener(e -> System.out.println("Button clicked!"));
// ✅ Lambda with multiple statements
button.addActionListener(e -> {
System.out.println("Button clicked at: " + System.currentTimeMillis());
JOptionPane.showMessageDialog(frame, "Hello Lambda!");
});
frame.add(button);
frame.setSize(300, 200);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
}
3. Runnable và Thread
public class RunnableExample {
public static void main(String[] args) {
// ❌ Old way
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("Thread 1 running");
}
});
// ✅ Lambda way
Thread thread2 = new Thread(() -> System.out.println("Thread 2 running"));
// ✅ Lambda with block
Thread thread3 = new Thread(() -> {
for (int i = 1; i <= 5; i++) {
System.out.println("Thread 3: " + i);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
thread1.start();
thread2.start();
thread3.start();
}
}
4. Custom Functional Interface
@FunctionalInterface
interface MathOperation {
int operate(int a, int b);
}
@FunctionalInterface
interface StringProcessor {
String process(String input);
}
public class CustomFunctionalInterfaceExample {
public static void main(String[] args) {
// MathOperation
MathOperation addition = (a, b) -> a + b;
MathOperation subtraction = (a, b) -> a - b;
MathOperation multiplication = (a, b) -> a * b;
System.out.println("10 + 5 = " + addition.operate(10, 5));
System.out.println("10 - 5 = " + subtraction.operate(10, 5));
System.out.println("10 * 5 = " + multiplication.operate(10, 5));
// StringProcessor
StringProcessor toUpperCase = s -> s.toUpperCase();
StringProcessor reverse = s -> new StringBuilder(s).reverse().toString();
StringProcessor addPrefix = s -> "Processed: " + s;
String input = "hello";
System.out.println(toUpperCase.process(input)); // HELLO
System.out.println(reverse.process(input)); // olleh
System.out.println(addPrefix.process(input)); // Processed: hello
}
}
Best Practices
✅ DO
// 1. Keep lambdas short (1-3 lines)
list.forEach(item -> System.out.println(item));
// 2. Use method references khi có thể
list.forEach(System.out::println); // Thay vì: item -> System.out.println(item)
// 3. Descriptive parameter names
employees.sort((emp1, emp2) -> emp1.getAge() - emp2.getAge());
// 4. Extract complex lambdas to methods
Predicate<Employee> isHighEarner = this::checkIfHighEarner;
list.stream().filter(isHighEarner);
❌ DON'T
// 1. Avoid long lambdas - extract to method
list.forEach(item -> {
// 50 lines of code... ❌ Too long!
});
// 2. Don't use single-letter names for complex logic
employees.filter(e -> e.getAge() > 30 && e.getSalary() > 5000); // ❌ 'e' unclear in complex logic
// 3. Don't modify external state in lambdas
int[] sum = {0};
list.forEach(x -> sum[0] += x); // ❌ Side effect
// 4. Don't use lambdas for recursion
// ❌ Lambdas không thể gọi chính nó (không có tên)
Tóm tắt
- Lambda expression là anonymous function, cú pháp:
(params) -> expression/block - Parameter styles: No param
(), single paramx, multiple params(a, b) - Body types: Expression body
x -> x * 2, block bodyx -> { return x * 2; } - Variable capture: Chỉ capture effectively final variables
thiskeyword: Refers to enclosing class, không phải lambda- vs Anonymous class: Lambda ngắn gọn hơn, nhanh hơn, nhưng chỉ cho functional interfaces
Thử thách: Output là gì?
Câu 1: Effectively final
int x = 10;
Runnable r1 = () -> System.out.println(x);
// x = 20; // Dòng này bị comment
Runnable r2 = () -> System.out.println(x);
r2.run();
r1.run();
Đáp án
Output:
10
10
Cả hai lambda đều bắt giữ giá trị x = 10. Vì x không bao giờ bị gán lại nên nó effectively final. Thứ tự output là r2 rồi r1 vì r2.run() được gọi trước.
Câu 2: this trong lambda vs anonymous class
public class Quiz {
String name = "Outer";
void test() {
Runnable lambda = () -> System.out.println(this.name);
Runnable anon = new Runnable() {
String name = "Inner";
public void run() { System.out.println(this.name); }
};
lambda.run();
anon.run();
}
public static void main(String[] args) {
new Quiz().test();
}
}
Đáp án
Output:
Outer
Inner
Trong lambda, this trỏ đến instance của lớp bao ngoài (Quiz), nên this.name là "Outer". Trong anonymous class, this trỏ đến chính anonymous class, nên this.name là "Inner".
Câu 3: Suy luận kiểu
// Dòng nào KHÔNG compile?
Object o1 = (Runnable) () -> System.out.println("A"); // Dòng 1
Object o2 = (Callable<Integer>) () -> 42; // Dòng 2
// Object o3 = () -> System.out.println("C"); // Dòng 3
Runnable o4 = () -> System.out.println("D"); // Dòng 4
Đáp án
Dòng 3 không compile (đã bị comment). Lambda phải có target type — Object không phải functional interface nên compiler không thể suy luận kiểu cho lambda. Dòng 1 và 2 compile được nhờ cast tường minh. Dòng 4 compile vì Runnable là functional interface.
Bài tập
Bài 1: Viết Lambda Expressions
Viết lambda expressions cho các functional interfaces sau:
// 1. Predicate<Integer>: kiểm tra số chẵn
Predicate<Integer> isEven = // TODO
// 2. Function<String, Integer>: độ dài string
Function<String, Integer> stringLength = // TODO
// 3. BiFunction<Integer, Integer, Integer>: tìm max
BiFunction<Integer, Integer, Integer> max = // TODO
// 4. Consumer<List<String>>: in tất cả elements
Consumer<List<String>> printAll = // TODO
Bài 2: Convert Anonymous Class to Lambda
Convert anonymous classes sau sang lambda:
// 1.
Comparator<String> comp = new Comparator<String>() {
@Override
public int compare(String s1, String s2) {
return s2.compareTo(s1);
}
};
// 2.
Thread t = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(i);
}
}
});
Bài 3: Effectively Final
Sửa code sau để compile thành công:
public void processData(List<Integer> numbers) {
int threshold = 10;
numbers.forEach(n -> {
if (n > threshold) {
System.out.println(n);
}
});
threshold = 20; // ❌ Error: variable used in lambda should be final
numbers.forEach(n -> {
if (n > threshold) {
System.out.println(n);
}
});
}
Bài tiếp theo: Functional Interfaces →
Tài liệu tham khảo
Sách
- Modern Java in Action (Manning, 2019) — Ch.3: Lambda Expressions
- Lambda syntax, method references, type inference
- Functional-style error handling with checked exceptions
- Best practices and performance considerations
- Effective Java 3rd Edition — Item 42, 43
- Item 42: Prefer lambdas to anonymous classes
- Item 43: Prefer method references to lambdas
Specifications & Official Docs
- Oracle: Lambda Expressions Tutorial
- JLS §15.27 — Lambda Expressions
- JLS §4.12.4 — final Variables (effectively final)
- JEP 276 — Dynamic Linking of Language-Defined Object Models
Bài học liên quan
- Tiếp theo: Functional Interfaces — Predicate, Function, Consumer, Supplier
- Liên quan: Method References — Cú pháp ngắn gọn hơn lambda
- Nâng cao: Stream API Basics — Áp dụng lambda với streams