1. Lambda expresstion là gì?
Lambda expression
là một feature mới và nổi bật nhất của java 8 giúp số lượng code được giảm và là bước đầu khiến Java bước vào thế giới functional programming
. Một lamba expression là một function được tạo mà không thuộc bất kì một class nào. Nó là một hàm không tên (unamed function) với các tham số (parameters) và phần body chứa khối lệnh được tách biệt với các tham số bằng dấu ->
. 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) // Có 2 tham số kiểu int và trả về tổng của chúng
---
(String msg) -> {System.out.printl(msg);} // Có một tham số kiểu String và in ra console
---
msg -> System.out.println(msg) // Có một tham số không cần xác định kiểu và in ra console
---
() -> "Hi" // Không có tham số và trả về một chuỗi
---
(int x, int y) -> {
int max = x > y ? x:y;
return max;
} // 2 tham số và trả về số lớn hơn.
Lambda expression thường được sử dụng để implement những event listeners/callback
đơn giản hoặc trong functional programming
cùng với Java Streams API
.
2. Functional interface là gì?
Một interface chỉ chứa duy nhất một method trừu tượng được gọi là function interface
. Nó cũng có thể được gọi là Single Abstract Interface
(SAM). Functional interface sử dụng annotation @FunctionalInterface
để khai báo. Tuy nhiên việc này là không bắt buộc, nó chỉ đảm bảo cho quá trình complile, giúp chương trình báo lỗi khi có từ 2 method trừu tượng trở lên.
Để sử dụng biểu thức Lambda thì đầu tiên cần phải có một functional interface chứa một phương thức cần overide lại trong biểu thức lambda. Ví dụ interface Runnable
là một functional interface:
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
Trước java 8 thì có vài cách để implement method trong một interface:
- Cách 1: Tạo một class mới:
public class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("I have implemented Runnable!");
}
public static void main(String args[]) {
MyRunnable runnable = new MyRunnable();
runnable.run();
}
}
- Cách 2: Tạo một class Annonymous:
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println("I have implemented Runnable!");
}
};
runnable.run();
Còn nếu chúng ta sử dụng biểu thức lambda thì chỉ cần code đơn giản như sau:
Runnable runnable = () -> System.out.println("I have implemented Runnable!");
runnable.run();
3. Funtional Interface API trong Java 8
Java 8 xây dựng sẵn một số functional interface được sử dụng trong các biểu thức lambda:
3.1 java.util.function.Consumer
package java.util.function;
import java.util.Objects;
@FunctionalInterface
public interface Consumer<T> {
// Phương thức chấp nhận một tham số đầu vào
// và không trả về gì cả.
void accept(T t);
}
Sử dụng khi: Consumer thường được dùng với list, stream để xử lý các phần tử bên trong.
Ví dụ đoạn code sau in ra tất cả các giá trị của 1 list:
List<String> list = Arrays.asList("tu", "bean", "tubean.github.io");
// Sử dụng List.forEach(Consumer) để in ra giá trị của các phần tử trong list
list.forEach(new Consumer<String>() {
@Override
public void accept(String t) {
System.out.println(t);
}
});
System.out.println("----------------");
// Sử dụng List.forEach(Consumer) với cú pháp lambda expression:
list.forEach(t -> System.out.println(t));
3.2 java.util.function.Predicate
package java.util.function;
import java.util.Objects;
@FunctionalInterface
public interface Predicate<T> {
// Kiểm tra một tham số đầu vào và trả về true hoặc false.
boolean test(T t);
}
** Sử dụng khi: ** Kiểm tra từng phần tử lúc xoá, lọc,… dùng với list, stream… Ví dụ:
List<Integer> list = new ArrayList<>();
list.add(-1);
list.add(1);
list.add(0);
list.add(-2);
list.add(3);
// lệnh removeIf sẽ thực hiện duyệt từng phần tử,
// nếu method test của Predicate trả về true thì sẽ remove phần tử đó khỏi list
list.removeIf(new Predicate<Integer>() {
@Override
public boolean test(Integer t) {
return t < 0;
}
});
list.forEach(t -> System.out.println(t));
System.out.println("-----------------------------");
// Sử dụng Predicate với cú pháp Lambda Expression
// loại bỏ các phần tử lớn hơn 1
list.removeIf(t -> t > 1);
list.forEach(t -> System.out.println(t));
3.3 java.util.function.Function
package java.util.function;
import java.util.Objects;
@FunctionalInterface
public interface Function<T, R> {
// Method này nhận đầu vào là 1 tham số và trả về một giá trị.
R apply(T t);
}
** Sử dụng khi: ** Function thường được dùng với Stream khi muốn thay đổi giá trị cho từng phần tử trong stream. Ví dụ:
List<String> list = Arrays.asList("tubean", "JAVA", "demo", "Function");
Stream<String> stream = list.stream();
// chuyển tất cả các phần tử của stream thành chữ in hoa
stream.map(new Function<String, String>() {
@Override
public String apply(String t) {
return t.toUpperCase();
}
}).forEach(t -> System.out.println(t));
System.out.println("---------------");
// Function với cú pháp Lambda Expression
// chuyển tất cả các phần tử của stream thành chữ thường
stream = list.stream();// lưu ý là stream ko thể dùng lại nên phải khởi tạo lại
stream.map(t -> t.toLowerCase()).forEach(t -> System.out.println(t))
Một số Function interface tương tự:
- java.util.function.IntFunction
: dữ liệu chuyển về kiểu Integer - java.util.function.DoubleFunction
: dữ liệu chuyển về kiểu Double - java.util.function.LongFunction
: dữ liệu chuyển về kiểu Long
3.4 java.util.function.Supplier
package java.util.function;
@FunctionalInterface
public interface Supplier<T> {
// method này không có tham số nhưng trả về một kết quả.
T get();
}
Ví dụ:
Random random = new Random();
Stream<Integer> stream = Stream.generate(new Supplier<Integer>() {
@Override
public Integer get() {
return random.nextInt(10);
}
}).limit(5);
stream.forEach(t -> System.out.print(t +" "));
System.out.println("\n--------------------");
// Sử dụng Supplier với cú pháp Lambda Expression:
stream = Stream.generate(() -> random.nextInt(10)).limit(5);
stream.forEach(t -> System.out.print(t +" "));
4. Interface có default và static method
Từ phiên bản java 8 thì các Interface
còn có thể có default methods
và static method
. Cả 2 loại method này đều được định nghĩa implementation ngay trong interface. Điều đó có nghĩa là, một lambda expression có thể implement một interface với nhiều hơn một method. Hay nói cách khác, nếu một interface có chứa cả default method và static method thì nó vẫn được coi là một functional interface, miễn là nó chỉ có duy nhất một abtract method.
Ví dụ:
import java.io.IOException;
import java.io.OutputStream;
public interface MyInterface {
void printIt(String text);
default public void printUtf8To(String text, OutputStream outputStream){
try {
outputStream.write(text.getBytes("UTF-8"));
} catch (IOException e) {
throw new RuntimeException("Error writing String as UTF-8 to OutputStream", e);
}
}
static void printItToSystemOut(String text){
System.out.println(text);
}
}
và lambda sẽ trông như sau:
MyInterface myInterface = (String text) -> {
System.out.print(text);
};
5. Lambda Expression và Anonymous Interface Implementations
Ngay cả khi lambda expression trông có vẻ giống anonymous interface implementations (AII) thì chúng cũng có vài điều khác biệt. Trong đó, điểm khác biệt lớn nhất chính là một AII có thể có các biến local trong khi đó một lambda expression thì không có. Cùng xem ví dụ dưới đây:
public interface MyEventConsumer {
// interface MyEventConsumner có một abtract method 1 parameter
public void consume(Object event);
}
Interface MyEventConsumer
được implement từ một AII như sau:
MyEventConsumer consumer = new MyEventConsumer() {
public void consume(Object event){
System.out.println(event.toString() + " consumed");
}
};
AII MyeventConsumer
có thể có khai báo thêm biến local của riêng nó như sau:
MyEventConsumer myEventConsumer = new MyEventConsumer() {
private int eventCount = 0; // biến khai báo trong AII, không thể khai báo như này nếu dùng lambda
public void consume(Object event) {
System.out.println(event.toString() + " consumed " + this.eventCount++ + " times.");
}
};
6: Variable Capture
Tuy rằng không thể khai báo biến bên trong biểu thức lambda, nhưng nó vẫn cho phép các biến được khai báo bên ngoài được sử dụng bình thường trong phần body. Lambda có thể capture được các loại biến như sau: 1. Local Variable Capture
// Chúng ta có một interface MyFactory
public interface MyFactory {
public String create(char[] chars);
}
// Lambda như này
MyFactory myFactory = (chars) -> {
return new String(chars);
};
// Capture local variable
String myString = "Test";
MyFactory myFactory = (chars) -> {
return myString + ":" + new String(chars);
};
- Instance Variable Capture
public class EventConsumerImpl {
private String name = "MyConsumer";
public void attach(MyEventProducer eventProducer){
eventProducer.listen(e -> {
System.out.println(this.name);
});
}
}
- Static Variable Capture
public class EventConsumerImpl {
private static String someStaticVar = "Some text";
public void attach(MyEventProducer eventProducer){
eventProducer.listen(e -> {
System.out.println(someStaticVar);
});
}
}
7. Sử dụng các phương thức tham chiếu (Method Referencees) là Lambda
Giả sử chúng ta có một interface MyPrinter
với một method trừu tượng truyền vào một parameter s:
public interface MyPrinter{
public void print(String s);
}
Thông thường chúng ta sẽ dùng lambda như sau:
MyPrinter myPrinter = (s) -> { System.out.println(s); };
Tuy nhiên, do biểu thức lambda chỉ có một câu lệnh, nên dấu {}
có thể bỏ qua. Ta rút gọn lại thành:
MyPrinter myPrinter = s -> System.out.println(s);
Bởi vì tham số truyền vào là s
và được sử dụng để làm tham số trong biểu thức lambda luôn nên java cho phép chúng ta rút gọn lại nữa như sau:
MyPrinter myPrinter = System.out::println;
Dấu ::
cho Java compiler biết đây là một method reference.
Chúng ta có các loại method reference sau:
- Static method references
// khai báo interface
public interface Finder {
public int find(String s1, String s2);
}
// khai báo class MyClass có một phương thức static có cùng signature với Interface
public class MyClass{
public static int doFind(String s1, String s2){
return s1.lastIndexOf(s2);
}
}
// biểu thức lambda như sau:
Finder finder = MyClass::doFind;
- Parameter Method Reference
public interface Finder {
public int find(String s1, String s2);
}
// Biểu thức lambda
Finder finder = String::indexOf;
// Dòng code trên có thể được viết dễ hiểu hơn như sau:
Finder finder = (s1, s2) -> s1.indexOf(s2);
- Instance Method References
// Interface
public interface Deserializer {
public int deserialize(String v1);
}
// Class StringConverter nào đó có method converToInt cùng signature với method trong interface
public class StringConverter {
public int convertToInt(String v1){
return Integer.valueOf(v1);
}
}
// Tạo một instance của class StringConverter
StringConverter stringConverter = new StringConverter();
// Biểu thức lambda implement interface
Deserializer des = stringConverter::convertToInt;
- Constructor References
// Interface
public interface Factory {
public String create(char[] val);
}
// Thay vì viết như này:
Factory factory = chars -> new String(chars);
// Ta viết như này:
Factory factory = String::new;
Tham khảo :