Java 8 Method Reference (Phần 1)
Bài đăng này đã không được cập nhật trong 6 năm
Giới thiệu
Trong Java, ta có thể sử dụng references tới các object hoặc tạo mới một object
List list = new ArrayList();
store(new ArrayList());
Hoặc
List list2 = list;
isFull(list2);
Thế nhưng một reference tới một method thì sẽ như thế nào ? Nếu mà ta chỉ sử dung một method của một object trong một method khác, ta vẫn phải truyền đẩy đủ object đó trong argument. Chẳng phải sẽ tốt hơn nếu ta chỉ cần truyền argument là chính method đó thôi phải không
isFull(list.size);
Trong Java 8, nhờ có lambda expression, ta có thể thực hiện được việc này, ta có thể coi các method như là một objects hoặc là một biến primitive
Java 8 Method reference
Một method reference là cú pháp ngắn của lambda expression giúp thực thi một method. Dưới đây là cú pháp chung cho một method reference:
Object :: methodName
Lambda expression được sử dụng để thay thế việc diễn đạt một anonymous class, đôi lúc nó được dùng chỉ để gọi method
Consumer<String> c = s -> System.out.println(s);
Để code được “rõ ràng”, ta có thể đưa lambda expression thành method preference
Consumer<String> c = System.out::println;
Trong method reference, ta đặt object (hoặc class) chứa method đó ở trước operator “::” và sau là tên của method không có kèm argument
Đến đây, bạn sẽ tự hỏi - Làm thế nào mà code lại “rõ ràng” hơn ? - Điều gì xảy ra cho các argument ? - Thế nào mà đoạn code này lại hợp lệ ? - Và làm thế nào để tạo một method preference ?
Điều đầu tiên chúng ta nên ghi nhớ, đó là method reference không thể sử dụng cho nhiều method. Chúng được tạo ra để thay thể một lambda expression sử dụng một method.
Từ đây ta suy ra được, để sử dụng method reference trước hết ta cần có lambda expression với một method. Để sử dụng lambda expression, cần có functional interface - một interface chỉ có duy nhất một abstract method.
Nói cách khác : Thay vì sử dụng : Anonymous Class Có thể sử dụng : Lambda Expression Nếu chỉ gọi một method trong lambda thì có thể sử dụng : Method reference
Có bốn loại method preference Method reference một static method Method reference một instance method của một object có kiểu đặc biệt. Method reference một instance method của một object đã tồn tại. Method reference một constructor
Static method reference
Ta có một lambda expression như sau:
(args) -> Class.staticMethod(args)
Có thể chuyển thành method reference như sau
Class::staticMethod
Ghi nhớ : ta sử dụng operator “::” giữa class và static method, và không truyền argument trongg static method. Kiểu của argument sẽ phụ thuộc vào kiểu của method reference, ở trường hợp này hành động truyền argument được ẩn đi (việc truyền vào sẽ tự động thực thi). Ta có thể chọn sử dụng lambda expression hoặc method reference khi gọi một static method
class Numbers {
public static boolean isMoreThanFifty(int n1, int n2) {
return (n1 + n2) > 50;
}
public static List<Integer> findNumbers( List<Integer> l, BiPredicate<Integer, Integer> p) {
List<Integer> newList = new ArrayList<>();
for(Integer i : l) {
if(p.test(i, i + 10)) {
newList.add(i);
}
}
return newList;
}
}
Ta có thể gọi findNUmbers bằng những cách sau
List<Integer> list = Arrays.asList(12,5,45,18,33,24,40);
// Using an anonymous class
findNumbers(list, new BiPredicate<Integer, Integer>() {
public boolean test(Integer i1, Integer i2) {
return Numbers.isMoreThanFifty(i1, i2);
}
});
// Using a lambda expression
findNumbers(list, (i1, i2) -> Numbers.isMoreThanFifty(i1, i2));
// Using a method reference
findNumbers(list, Numbers::isMoreThanFifty);
Instance method reference một object có kiểu đặc biệt
Kiểu đặc biệt ở đây theo mình hiểu là những class mà mình tự tạo ra.
Ta có lambda expression như sau :
(obj, args) -> obj.instanceMethod(args)
Trong đó instance của object được truyền vào, và một trong những method của nó được thực th với các parameters tùy chọn (optional).
Ta có thể đưa về method reference như sau
ObjectType::instanceMethod
Lúc này, trong method reference ta không sử dụng bản thân instance đó mà sử dụng kiểu của nó. Kế đó, argument trong lambda expression (args) nếu có được sử dụng thì cũng hành động truyền vào cũng sẽ ẩn đi như đối với static method.
Ta có ví dụ sau:
class Shipment {
public double calculateWeight() {
double weight = 0;
// Calculate weight
return weight;
}
}
public List<Double> calculateOnShipments(
List<Shipment> l, Function<Shipment, Double> f) {
List<Double> results = new ArrayList<>();
for(Shipment s : l) {
results.add(f.apply(s));
}
return results;
}
Ta có thể gọi method như sau
List<Shipment> l = new ArrayList<Shipment>();
// Using an anonymous class
calculateOnShipments(l, new Function<Shipment, Double>() {
public Double apply(Shipment s) { // The object
return s.calculateWeight(); // The method
}
});
// Using a lambda expression
calculateOnShipments(l, s -> s.calculateWeight());
// Using a method reference
calculateOnShipments(l, Shipment::calculateWeight);
Ở ví dụ này, ta không truyền argument nào. Điểm chính ở ví dụ này chính là instance của object Shipment là parameter trong lambda expression và ta lấy reference của instance method đó thông qua kiểu của instance (calculateWeight từ Shipment).
Sau đây sẽ là ví dụ khi ta truyền 2 argument trong method reference Java có Function interface nhận 1 parameter, BiFunction để nhận 2 parameter nhưng không có TriFunction để nhận 3 parameter. Ta sẽ tạo ra một cái.
interface TriFunction<T, U, V, R> {
R apply(T t, U u, V v);
}
Giờ ta tạo môt class với một method nhận 2 parameter để giả về kết quả:
class Sum {
Integer doSum(String s1, String s2) {
return Integer.parseInt(s1) + Integer.parseInt(s1);
}
}
Ta sẽ wrap doSum() method trong TriFunction bằng cách sử dụng anonymous class
TriFunction<Sum, String, String, Integer> anon =
new TriFunction<Sum, String, String, Integer>() {
@Override
public Integer apply(Sum s, String arg1, String arg2) {
return s.doSum(arg1, arg1);
}
};
System.out.println(anon.apply(new Sum(), "1", "4"));
Bằng cách sử dụng lambda expression, ta làm ngắn gọn code như sau
riFunction<Sum, String, String, Integer> lambda =
(Sum s, String arg1, String arg2) -> s.doSum(arg1, arg1);
System.out.println(lambda.apply(new Sum(), "1", "4"));
Hoặc sử dụng method reference
TriFunction<Sum, String, String, Integer> mRef = Sum::doSum;
System.out.println(mRef.apply(new Sum(), "1", "4"));
- Parameter đầu tiên của TriFunction là kiểu object chưa method để thực thi
- Parameter thứ 2 là kiểu của parameter thứ nhất
- Parameter thứ 3 là kiểu của parameter thứ 2
- Parameter cuối cùng là kiểu trả về của method được thực thi, trong lambda expression và method reference thì việc hiển thị giá trị này được bỏ qua.
- Nếu chỉ nhìn method reference thì sẽ khó hình dung cách sử dụng của nó, vì vậy ta cần nhìn vào anonymous class hay lambda expression. Từ (Sum s, String arg1, String arg2) -> s.doSum(arg1, arg1) Thành Sum::doSum
Ở phần này mình đã giới thiệu qua 2 loại method reference đầu tiên, 2 loại còn lại sẽ được giới thiệu ở phần kế tiếp, cảm ơn các bạn đã đọc.
All rights reserved