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 là 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
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 vì equals đã là 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 vì Comparator chỉ có một phương thức
trừu tượng non-Object
interface Foo {
int m();
Object clone();
}
// Không
hoạt động vì 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ó cùng một
chữ ký
interface X { Iterable m(Iterable<String> arg); }
interface Y { Iterable<String> m(Iterable arg); }
interface Z extends X, Y {}
//
Functional: Y.m là một chữ ký con & kiểu
trả về có 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
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 là (đố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");
Ø 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.
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;
}
static boolean isPrimeNumb(int numb) {
return numb > 1
&&
IntStream.range(2, numb).noneMatch(
index -> numb % index ==0);
}
static boolean isPrimeNumb(int numb) {
return numb > 1
&& IntStream.range(2,
numb).noneMatch(
index -> numb % index ==0);
}
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();
}
//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)
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

0 Nhận xét