Header Ads Widget

Responsive Advertisement

Nổi bật

6/recent/ticker-posts

Functional interface trong Java 8

Chào mừng bạn đến với hướng dẫn ví dụ về Functional interface trong Java 8. Java luôn là một ngôn ngữ lập trình hướng đối tượng. Điều gì có nghĩa là mọi thứ trong lập trình java đều xoay quanh các Object (ngoại trừ một số kiểu nguyên thủy để đơn giản hóa). Chúng ta không chỉ có các hàm trong java, chúng là một phần của Class và chúng ta cần sử dụng class/object để gọi bất kỳ hàm nào.


1. Các Functional interface của Java 8

Nếu chúng ta xem xét một số ngôn ngữ lập trình khác như C ++, JavaScript; chúng được gọi là ngôn ngữ lập trình chức năng vì chúng ta có thể viết các hàm và sử dụng chúng khi được yêu cầu. Một số ngôn ngữ này hỗ trợ Lập trình hướng đối tượng cũng như Lập trình chức năng.

Hướng đối tượng không phải là xấu, nhưng nó mang lại rất nhiều chi tiết cho chương trình. Ví dụ, giả sử chúng ta phải tạo một phiên bản Runnable. Thông thường chúng tôi làm điều đó bằng cách sử dụng các lớp ẩn danh như bên dưới.

Runnable r = new Runnable(){

     @Override

     public void run() {

           System.out.println("My Runnable");

}};

Nếu bạn nhìn vào đoạn mã trên, phần thực sự được sử dụng là đoạn mã bên trong phương thức run(). Phần còn lại của tất cả mã là do cách các chương trình java được cấu trúc.

Các Functional interface và biểu thức Lambda của Java 8 giúp chúng ta viết mã nhỏ hơn và sạch hơn bằng cách loại bỏ rất nhiều mã boiler-plate.

2.    Functional interface Java 8

Một interface với chính xác một phương thức trừu tượng được gọi là Functional interface. Chú thích @FunctionalInterface được thêm vào để chúng ta có thể đánh dấu một interface Functional interface.

Không bắt buộc phải sử dụng nó, nhưng cách tốt nhất là sử dụng nó với các Functional interface để tránh vô tình thêm các phương thức bổ sung. Nếu interface được chú thích bằng @FunctionalInterface và chúng tôi cố gắng có nhiều hơn một phương thức trừu tượng, nó sẽ gây ra lỗi trình biên dịch.

Lợi ích chính của Functional interface trong Java 8 là chúng ta có thể sử dụng các biểu thức lambda để khởi tạo chúng và tránh sử dụng triển khai lớp ẩn danh cồng kềnh.

Java 8 Collection API đã được viết lại và Stream API mới được giới thiệu sử dụng rất nhiều Functional interface. Java 8 đã định nghĩa rất nhiều Functional interface trong gói java.util.function. Một số Functional interface trong Java 8 hữu ích là Consumer, Supplier, Function và Predicate.1
Bạn có thể tìm thêm thông tin chi tiết về chúng trong Ví dụ về Stream Java 8 .

java.lang.Runnable là một ví dụ tuyệt vời về Functional interface với một phương thức trừu tượng run().
Đoạn mã dưới đây cung cấp một số hướng dẫn cho các Functional interface:


interface Foo { boolean equals(Object obj); }

       // Không hoạt động equals đã một thành viên ngầm định (Object class)

       interface Comparator<T> {

        boolean equals(Object obj);

        int compare(T o1, T o2);

       }

       // Functional bởi Comparator chỉ một phương thức trừu tượng non-Object

       interface Foo {

         int m();

         Object clone();

       }

       // Không hoạt động phương thức Object.clone không công khai

 

       interface X { int m(Iterable<String> arg); }

       interface Y { int m(Iterable<String> arg); }

       interface Z extends X, Y {}

       // Function: hai phương thức, nhưng chúng cùng một chữ

       interface X { Iterable m(Iterable<String> arg); }

       interface Y { Iterable<String> m(Iterable arg); }

       interface Z extends X, Y {}

       // Functional: Y.m một chữ con & kiểu trả về thể thay thế

       interface X { int m(Iterable<String> arg); }

       interface Y { int m(Iterable<Integer> arg); }

       interface Z extends X, Y {}

       // Not functional: No method has a subsignature of all abstract methods

 

       interface X { int m(Iterable<String> arg, Class c); }

       interface Y { int m(Iterable arg, Class<?> c); }

       interface Z extends X, Y {}

       // Not functional: No method has a subsignature of all abstract methods

 

       interface X { long m(); }

       interface Y { int m(); }

       interface Z extends X, Y {}

       // Compiler error: no method is return type substitutable

 

       interface Foo<T> { void m(T arg); }

       interface Bar<T> { void m(T arg); }

       interface FooBar<X, Y> extends Foo<X>, Bar<Y> {}

          // Compiler error: different signatures, same erasure

3.    Lambda Expression

Lambda Expression là cách mà chúng ta có thể hình dung lập trình hàm trong thế giới hướng đối tượng java. Đối tượng là cơ sở của ngôn ngữ lập trình java và chúng ta không bao giờ có thể có một hàm mà không có Đối tượng, đó là lý do tại sao ngôn ngữ Java cung cấp hỗ trợ chỉ sử dụng các biểu thức lambda với các functional interfaces.

Vì chỉ có một hàm trừu tượng trong các functional interfaces, nên không có sự nhầm lẫn trong việc áp dụng biểu thức lambda cho phương thức. Cú pháp Lambda Expressions(đối số) -> (nội dung). Bây giờ chúng ta hãy xem cách chúng ta có thể viết Runnable ẩn danh trên bằng cách sử dụng biểu thức lambda.

Runnable r1 = () -> System.out.println("My Runnable");

Chúng ta hãy cố gắng hiểu những gì đang xảy ra trong biểu thức lambda ở trên.

Ø  Runnable là một Functional interface, đó là lý do tại sao chúng ta có thể sử dụng biểu thức lambda để tạo phiên bản của nó.

Ø  Vì phương thức run() không có đối số, biểu thức lambda của chúng ta cũng không có đối số.

Ø  Cũng giống như các khối if-else, chúng ta có thể tránh dấu ngoặc nhọn ({}) vì chúng ta có một câu lệnh duy nhất trong thân phương thức. Đối với nhiều câu lệnh, chúng ta sẽ phải sử dụng dấu ngoặc nhọn giống như bất kỳ phương thức nào khác.

4.    Tại sao chúng ta cần Lambda Expression

1. Giảm dòng mã

Một trong những lợi ích rõ ràng của việc sử dụng biểu thức lambda là số lượng mã được giảm bớt, chúng ta đã thấy rằng chúng ta có thể tạo phiên bản Functional interface dễ dàng như thế nào bằng cách sử dụng biểu thức lambda thay vì sử dụng lớp ẩn danh.

2. Hỗ trợ thực thi tuần tự và song song

Một lợi ích khác của việc sử dụng biểu thức lambda là chúng ta có thể hưởng lợi từ hỗ trợ các hoạt động song song và tuần tự của Stream API.

Để giải thích điều này, chúng ta hãy lấy một ví dụ đơn giản trong đó chúng ta cần viết một phương thức để kiểm tra xem một số được truyền vào có phải là số nguyên tố hay không.

Theo truyền thống, chúng tôi sẽ viết mã của nó như dưới đây. Mã không được tối ưu hóa hoàn toàn nhưng tốt cho mục đích ví dụ, vì vậy hãy nhớ với tôi về điều này.

       //Traditional approach

     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 đề với đoạn mã trên là nó có tính chất tuần tự, nếu số lượng rất lớn thì nó sẽ mất một lượng thời gian đáng kể. Một vấn đề khác với mã là có quá nhiều điểm thoát và nó không thể đọc được. Hãy xem cách chúng ta có thể viết cùng một phương thức bằng cách sử dụng biểu thức lambda Stream API.

       static boolean isPrimeNumb(int numb) {

                   return numb > 1

                                      && IntStream.range(2, numb).noneMatch(

                                                          index -> numb % index ==0);

          }


IntStream là một chuỗi các phần tử có giá trị int nguyên thủy hỗ trợ các phép toán tổng hợp tuần tự và song song. Đây là chuyên ngành nguyên thủy int của Stream. 
Để dễ đọc hơn, chúng ta cũng có thể viết phương thức như bên dưới.

static boolean isPrimeNumb(int numb) {

       return numb > 1

              && IntStream.range(2, numb).noneMatch(

              index -> numb % index ==0);

}


Nếu bạn không quen thuộc với IntStream, phương thức range() của nó trả về một IntStream được sắp xếp theo thứ tự từ startInclusive (bao gồm) đến endExclusive (độc quyền) theo từng bước tăng dần là 1. 
Phương thức noneMatch()  trả về liệu không có phần tử nào của Stream này khớp với vị từ đã cung cấp. Nó có thể không đánh giá vị từ trên tất cả các phần tử nếu không cần thiết để xác định kết quả. 

3. Truyền các Behaviors vào các phương pháp
Hãy xem cách chúng ta có thể sử dụng biểu thức lambda để chuyển hành vi của một phương thức với một ví dụ đơn giản. Giả sử chúng ta phải viết một phương thức để tính tổng các số trong danh sách nếu chúng phù hợp với một tiêu chí nhất định. Chúng ta có thể sử dụng Predicate và viết một phương thức như bên dưới.

public static int sumWithCondition(List<Integer> numb, Predicate<Integer> predicate ) {

       return numb.parallelStream()

              .filter(predicate).mapToInt(i -> i).sum();

}


Cách sử dụng mẫu:

          //sum of all numbers

          sumWithCondition(numbers, n -> true)

          //sum of all even numbers

          sumWithCondition(numbers, i -> i%2==0)

          //sum of all numbers greater than 5

          sumWithCondition(numbers, i -> i>5)

4. Hiệu quả cao hơn với sự lười biếng

Một lợi thế nữa của việc sử dụng biểu thức lambda là đánh giá lười biếng, ví dụ: giả sử chúng ta cần viết một phương thức để tìm ra số lẻ lớn nhất trong phạm vi từ 3 đến 11 và trả về bình phương của nó.

Thông thường chúng ta sẽ viết mã cho phương thức này như sau:

       private static int findSquareOfMaxOdd(List<Integer> 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 theo thứ tự tuần tự nhưng chúng ta có thể sử dụng Stream API để đạt được điều này và nhận được lợi ích của việc tìm kiếm sự lười biếng. Hãy xem cách chúng ta có thể viết lại mã này theo cách lập trình chức năng bằng cách sử dụng Stream API và biểu thức lambda.

public static int findSquareOfMaxOdd(List<Integer> numbers) {

       return numbers.stream()

              .filter(NumberTest::isOdd)       

//Predicate is functional interface and

              .filter(NumberTest::isGreaterThan3)     

// we are using lambdas to initialize it

              .filter(NumberTest::isLessThan11)

// rather than anonymous inner classes

                           .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;

       }


Nếu bạn ngạc nhiên với toán tử dấu hai chấm (: :), nó được giới thiệu trong Java 8 và được sử dụng cho các tham chiếu phương thức. Java compiler đảm nhận việc ánh xạ các đối số tới phương thức được gọi. Đó là dạng rút gọn của biểu thức lambda i -> isGreaterThan3 (i) hoặc i -> NumberTest.isGreaterThan3 (i).

5.    Ví dụ về biểu thức Lambda

Dưới đây, tôi cung cấp một số đoạn mã cho các biểu thức lambda với các chú thích nhỏ giải thích chúng.

() -> {}                     // No parameters; void result

 

() -> 42                     // No parameters, expression body

() -> null                   // No parameters, expression body

() -> { return 42; }         // No parameters, block body with return

() -> { System.gc(); }       // No parameters, void block body

 

// Complex block body with multiple returns

() -> {

  if (true) return 10;

  else {

    int result = 15;

    for (int i = 1; i < 10; i++)

      result *= i;

    return result;

  }

}                         

 

(int x) -> x+1             // Single declared-type argument

(int x) -> { return x+1; } // same as above

(x) -> x+1                 // Single inferred-type argument, same as below

x -> x+1                   // Parenthesis optional for single inferred-type case

 

(String s) -> s.length()   // Single declared-type argument

(Thread t) -> { t.start(); } // Single declared-type argument

s -> s.length()              // Single inferred-type argument

t -> { t.start(); }          // Single inferred-type argument

 

(int x, int y) -> x+y      // Multiple declared-type parameters

(x,y) -> x+y               // Multiple inferred-type parameters

(x, final y) -> x+y        // Illegal: can't modify inferred-type parameters

(x, int y) -> x+y          // Illegal: can't mix inferred and declared types

6.    Tài liệu tham khảo về Phương thức và Khối mã lệnh

Tham chiếu phương thức được sử dụng để tham chiếu đến một phương thức mà không cần gọi nó; một tham chiếu phương thức khởi tạo được sử dụng tương tự để tham chiếu đến một phương thức khởi tạo mà không cần tạo một thể hiện mới của lớp hoặc kiểu mảng được đặt tên.

Ví dụ về tham chiếu phương thức và hàm tạo:

System::getProperty

       System.out::println

       "abc"::length

       ArrayList::new

int[]::new


Đó là tất cả đối với Functional interface Java 8Hướng dẫn biểu thức Lambda. Tôi thực sự khuyên bạn nên xem xét sử dụng nó vì cú pháp này là mới đối với Java và sẽ mất một thời gian để nắm bắt nó.

Bài viết này được dịch từ Journaldev.com. Cảm ơn bạn đã xem bìa viết!

Đăng nhận xét

0 Nhận xét