CodeGym /Các khóa học /JAVA 25 SELF /Method References (::): tham chiếu tới phương thức

Method References (::): tham chiếu tới phương thức

JAVA 25 SELF
Mức độ , Bài học
Có sẵn

1. Giới thiệu

Method reference (tham chiếu phương thức) là cú pháp đặc biệt trong Java cho phép truyền một phương thức (hoặc constructor) đã tồn tại như là phần triển khai của một functional interface. Bạn có thể “truyền” phương thức vào nơi kỳ vọng một biểu thức lambda, miễn là chữ ký khớp nhau.

Cú pháp:

Class::method
object::method
Class::new

Nếu lambda là một “hàm mini” tạo tức thời, thì method reference đơn giản là “truyền phương thức đã có sẵn”. Nó giống như thay vì chép lại công thức nấu ăn, bạn đưa liên kết đến trang có công thức ấy.

Ví dụ dễ hiểu

Thay vì thế:

list.forEach(s -> System.out.println(s));

Có thể viết:

list.forEach(System.out::println);

Trông gọn gàng hơn, đúng không?

2. Các loại method reference

Có bốn dạng chính của tham chiếu phương thức. Thế là đủ cho hầu hết trường hợp.

Tham chiếu tới phương thức tĩnh

Cú pháp: Class::staticMethod

Ví dụ:

Function<Integer, String> intToString = String::valueOf;
System.out.println(intToString.apply(123)); // "123"

Tương đương với lambda:

Function<Integer, String> intToString = i -> String.valueOf(i);

Tham chiếu tới phương thức không tĩnh của đối tượng

Cú pháp: object::method

Ví dụ:

PrintStream printer = System.out;
Consumer<String> consumer = printer::println;
consumer.accept("Xin chào, thế giới!");

Tương đương với lambda:

Consumer<String> consumer = s -> printer.println(s);

Tham chiếu tới phương thức không tĩnh của lớp

Cú pháp: Class::method

Ở đây tham số đầu tiên của functional interface sẽ trở thành đối tượng gọi phương thức.

Ví dụ:

Function<String, Integer> stringLength = String::length;
System.out.println(stringLength.apply("Java")); // 4

Ở đây String::length trở thành hàm: (String s) -> s.length()

Tham chiếu tới constructor

Cú pháp: Class::new

Ví dụ:

Supplier<ArrayList<String>> listSupplier = ArrayList::new;
ArrayList<String> list = listSupplier.get();

Tương đương với lambda:

Supplier<ArrayList<String>> listSupplier = () -> new ArrayList<>();

3. Khi nào dùng method reference?

Method reference hữu ích khi lambda chỉ gọi một phương thức có sẵn mà không có thêm logic — như vậy mã sẽ ngắn gọn và dễ đọc hơn.

Ví dụ: sắp xếp danh sách

Thay vì thế:

List<String> names = Arrays.asList("Ivan", "Petr", "Anna");
names.sort((a, b) -> a.compareToIgnoreCase(b));

Có thể viết:

names.sort(String::compareToIgnoreCase);

Ví dụ: xử lý collection

Thay vì:

list.forEach(s -> System.out.println(s));

Có thể viết ngắn hơn nữa:

list.forEach(System.out::println);

Ví dụ: chuyển đổi phần tử

Thay vì:

List<String> numbers = Arrays.asList("1", "2", "3");
List<Integer> ints = numbers.stream()
    .map(s -> Integer.parseInt(s))
    .collect(Collectors.toList());

Có thể viết:

List<Integer> ints = numbers.stream()
    .map(Integer::parseInt)
    .collect(Collectors.toList());

Tìm hiểu thêm về Stream API và lớp tiện ích Collectors ở cấp 30 :P

4. So sánh method reference và biểu thức lambda

Tính tương đương

Method reference và biểu thức lambda thường có thể thay thế cho nhau. Cả hai đều triển khai functional interface nếu chữ ký khớp.

Ví dụ:

Consumer<String> c1 = s -> System.out.println(s);
Consumer<String> c2 = System.out::println;

Khi nào nên dùng method reference?

  • Khi lambda chỉ gọi một phương thức có sẵn mà không có logic bổ sung.
  • Để tăng tính dễ đọc, đặc biệt trong các chuỗi gọi phương thức dài.
  • Khi bạn muốn nhấn mạnh: “ở đây chỉ là lời gọi phương thức”.

Khi nào không nên dùng method reference?

  • Nếu cần logic bổ sung (kiểm tra, điều kiện, xử lý lỗi).
  • Nếu cần biến đổi tham số trước khi gọi phương thức.

Ví dụ:

list.forEach(s -> {
    if (s != null) System.out.println(s);
});
// Ở đây method reference không phù hợp, chỉ có lambda.

5. Thực hành: viết lại các biểu thức lambda bằng method reference

Ví dụ 1: In tên các loài động vật

List<String> animals = Arrays.asList("Mèo", "Chó", "Vẹt");
animals.forEach(animal -> System.out.println(animal));

Chuyển thành:

animals.forEach(System.out::println);

Ví dụ 2: Chuyển chuỗi thành số

List<String> numbers = Arrays.asList("10", "20", "30");
List<Integer> ints = numbers.stream()
    .map(s -> Integer.parseInt(s))
    .collect(Collectors.toList());

Chuyển thành:

List<Integer> ints = numbers.stream()
    .map(Integer::parseInt)
    .collect(Collectors.toList());

Ví dụ 3: Sắp xếp đối tượng theo tên

List<Animal> animalList = ...;
animalList.sort((a, b) -> a.getName().compareTo(b.getName()));

Chuyển thành:

animalList.sort(Comparator.comparing(Animal::getName));

Ở đây Animal::getName là tham chiếu đến phương thức không tĩnh của lớp.

Ví dụ 4: Tạo đối tượng thông qua constructor

Supplier<Dog> dogFactory = () -> new Dog();
Dog dog = dogFactory.get();

Chuyển thành:

Supplier<Dog> dogFactory = Dog::new;
Dog dog = dogFactory.get();

6. Cách hoạt động của việc khớp chữ ký

Method reference chỉ có thể dùng khi chữ ký của phương thức khớp với phương thức trừu tượng của functional interface.

@FunctionalInterface
interface IntToString {
    String convert(int value);
}

public class Demo {
    public static String intToHex(int value) {
        return Integer.toHexString(value);
    }

    public static void main(String[] args) {
        IntToString converter = Demo::intToHex;
        System.out.println(converter.convert(255)); // ff
    }
}

Ở đây Demo::intToHex phù hợp vì nhận int và trả về String.

7. Method reference và các constructor có tham số

Nếu constructor nhận tham số, bạn vẫn có thể dùng method reference — miễn là chữ ký khớp.

@FunctionalInterface
interface AnimalFactory {
    Animal create(String name);
}

class Animal {
    private String name;
    public Animal(String name) { this.name = name; }
    public String getName() { return name; }
}

AnimalFactory factory = Animal::new;
Animal cat = factory.create("Barsik");
System.out.println(cat.getName()); // Barsik

8. Các lỗi thường gặp khi dùng method reference

Lỗi số 1: Không khớp chữ ký.
Nếu chữ ký của phương thức không khớp với phương thức trừu tượng của interface, trình biên dịch sẽ báo lỗi. Ví dụ, interface kỳ vọng hai tham số nhưng tham chiếu lại chỉ ra phương thức có một tham số.

Lỗi số 2: Cố dùng tham chiếu tới phương thức không tĩnh của lớp mà không có đối tượng.
Khi dùng dạng Class::method, tham số đầu tiên của interface trở thành đối tượng để gọi phương thức. Nếu nhầm số lượng hoặc thứ tự tham số — bạn sẽ gặp lỗi khớp chữ ký.

Lỗi số 3: Dùng method reference ở nơi cần thêm logic.
Nếu cần điều kiện, ghi log hoặc xử lý ngoại lệ — hãy dùng lambda thay vì tham chiếu phương thức.

Lỗi số 4: Tham chiếu tới các phương thức bị nạp chồng.
Nếu một lớp có nhiều phương thức trùng tên, trình biên dịch có thể không biết chọn phương thức nào. Đôi khi cần chỉ rõ kiểu của functional interface để gợi ý.

Lỗi số 5: Dùng tham chiếu tới phương thức không tĩnh mà thiếu đối tượng.
Ví dụ, String::toUpperCase hoạt động đúng trong map vì tham số đầu tiên chính là đối tượng String. Nhưng ngoài ngữ cảnh phù hợp, nơi kỳ vọng phương thức tĩnh, điều này sẽ gây lỗi.

Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION