Giới thiệu Lambda Expression trong Java 8

1. Lambda Expression là gì?

Lamda Expression là một hàm không có tên (unamed function) với các tham số (parameters) và nội dung thực thi (body). Nội dung thực thi của Lamda expression có thể là 1 khối lệnh hoặc 1 biểu thức. Dấu “->” tách biệt các tham số và nội dung thực thi.

Ví dụ:

(int x) -> x + 1 Có một tham số kiểu int và trả về giá trị tham số tăng lên 1

(int x, int y) -> x + y Có 2 tham số kiểu int và trả về tổng

(String msg) -> {System.out.println (msg);} Có một tham số String và in nó ra console.

msg -> System.out.println (msg) Có một tham số không cần xác định kiểu và in nó ra console. Nó giống hệt với đoạn code ở ví dụ trên.

() -> "Hi" Không có tham số và trả về một chuỗi.

(String str) -> str.length() Có một tham số String và trả về chiều dài của nó.

(int x, int y) -> {
    int max = x > y ? x: y;
    return max;
}

2. Functional Interface

Mô tả

Functional Interface là một interface có một phương thức abstract, nó cũng có thể được gọi là Single Abstract Interface (SAM) một cụm từ đôi khi chúng ta bắt gặp. @FunctionalInterface annotation được thêm vào để chúng ta đánh dấu interface đó là functional interface, điều này không bắt buộc nhưng có thể là cách tốt nhất trong việc sử dụng functional interface để tránh vô tình thêm các phương thức khác. Nếu một interface đã có annotation @FunctionalInterface và chúng ta cố gắng thêm vào các phương thức khác thì trình biên dịch sẽ ném ra lỗi. Lợi ích chính của functional interface là chúng ta có thể sử dụng Lambda Expression để tạo ra thể hiện (instance) cho interface đó.

Ví dụ

Trong ví dụ sau, chúng ta sẽ gán biểu thức lambda cho functional interface. Sau đó chúng ta có thể chạy biểu thức lambda bằng cách gọi phương thức được định nghĩa bên trong functional interface và truyền tham số vào.

public class Main {
  public static void main(String[] argv) {
    Processor stringProcessor = (String str) -> str.length();
    String name = "Java Lambda";
    int length = stringProcessor.getStringLength(name);
    System.out.println(length);
 
  }
}
 
@FunctionalInterface
interface Processor {
  int getStringLength(String str);
}

3. Tại sao phải sử dụng Lambda Expression?

Giảm số dòng code

Một trong những tiện ích dễ thấy nhất khi sử dụng Lambda Expression là số lượng dòng code được giảm. Chúng ta có thể dễ dàng tạo ra một thể hiện của một functional interface bằng cách sử dụng Lambda Expression hơn là sử dụng một anonymous class.

Hỗ trợ thực hiện tuần tự (Sequential) và song song (Parallel)

Một tiện ích khác của Lambda Expression là việc hưởng lợi từ sự hỗ trợ của Stream API cho các tiến trình tuần tự và song song.

Để cho dễ hiểu, chúng ta cùng làm 1 ví dụ đơn giản, chúng ta sẽ viết một phương thức để kiểm tra xem số nhập vào có phải là số nguyên tố hay không.

Theo cách thông thường, ta sẽ viết code như sau, có thể đoạn code chưa tối ưu nhưng thể hiện được điều mình muốn làm

private static boolean isPrime(int number) {        
if(number < 2) return false;
for(int i=2; i<number; i++){
if(number % i == 0) return false;
}
return true;
}

Vấn đề ở đoạn code trên là nó sẽ chạy tuần tự một cách tự nhiên và nếu chúng ta có 1 số cực lớn thì chúng ta sẽ mất nhiều thời gian. Ngoài ra trong đoạn code này có nhiều điểm trả về mà nó sẽ không chạy qua.

Bây giờ chúng ta sẽ xem Lambda Expression giải quyết vấn đề này như thế nào.

private static boolean isPrime(int number) {        
    return number > 1
            && IntStream.range(2, number).noneMatch(
                    index -> number % index == 0);
}

IntStream là một chuỗi các phần tử có giá trị kiểu int hỗ trợ các tiến trình tổng hợp tuần tự và song song. Để dễ đọc hơn, chúng ta cũng có thể viết theo cách sau:

Java

private static boolean isPrime(int number) { IntPredicate isDivisible = index -> number % index == 0;

return number > 1
        && IntStream.range(2, number).noneMatch(
                isDivisible);

}

private static boolean isPrime(int number) {
    IntPredicate isDivisible = index -> number % index == 0;
      
    return number > 1
            && IntStream.range(2, number).noneMatch(
                    isDivisible);
}

Truyền hành động (behavior) vào phương thức Bây giờ chúng ta sẽ truyền hành động vào một phương thức thông qua một ví dụ đơn giản.

Cách sử dụng:

//Tổng các số
sumWithCondition(numbers, n -> true)
//Tổng các số chẵn
sumWithCondition(numbers, i -> i%2==0)
//Tổng các số lớn hơn 5
sumWithCondition(numbers, i -> i>5)

Lười biếng nhưng hiệu quả Một lợi thế của việc sử dụng Lambda Expression là sự lười biếng. Ví dụ, ta sẽ viết một phương thức tìm ra số lẻ lớn nhất trong dãy từ 3 đến 11 và trả về bình phương của nó.

Thông thường, đoạn code sẽ được viết như sau:

private static int findSquareOfMaxOdd(List numbers) {
        int max = 0;
        for (int i : numbers) {
            if (i % 2 != 0 && i > 3 && i < 11 && i > max) {
                max = i;
            }
        }
        return max * max;
}

Chương trình trên sẽ luôn chạy tuần tự nhưng chúng ta có thể sử dụng Stream API để đạt được điều này với cách lười biếng nhất. Hãy xem cách chúng ta viết lại khi sử dụng Stream API và Lambda Expression.

public static int findSquareOfMaxOdd(List numbers) {
    return numbers.stream()
            .filter(NumberTest::isOdd)          
            .filter(NumberTest::isGreaterThan3)
            .filter(NumberTest::isLessThan11)
            .max(Comparator.naturalOrder())
            .map(i -> i * i)
            .get();
}
  
public static boolean isOdd(int i) {
    return i % 2 != 0;
}
      
public static boolean isGreaterThan3(int i){
    return i > 3;
}
      
public static boolean isLessThan11(int i){
    return i < 11;
}

Tham khảo:: https://docs.oracle.com/javase/tutorial/java/javaOO/lambdaexpressions.html