Java 8 Method Reference (Phần 1)

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