Làm thế nào để các cuộc gọi lambda tương tác với Giao diện?


8

Đoạn mã hiển thị dưới đây hoạt động. Tuy nhiên, tôi không chắc tại sao nó hoạt động. Tôi không hoàn toàn tuân theo logic về cách hàm lambda truyền thông tin đến giao diện.

Kiểm soát được thông qua ở đâu? Làm thế nào là trình biên dịch có ý nghĩa của từng ntrong vòng lặp và từng messageđược tạo ra?

Mã này biên dịch và đưa ra kết quả mong đợi. Tôi chỉ không chắc làm thế nào.

import java.util.ArrayList;
import java.util.List;

public class TesterClass {

    public static void main(String[] args) {

        List<String> names = new ArrayList<>();

        names.add("Akira");
        names.add("Jacky");
        names.add("Sarah");
        names.add("Wolf");

        names.forEach((n) -> {
            SayHello hello = (message) -> System.out.println("Hello " + message);
            hello.speak(n);
        });
    }

    interface SayHello {
        void speak(String message);
    }
}

Câu trả lời:


8

Các SayHellolà một giao diện đơn Tóm tắt phương pháp trong đó có một phương pháp mà phải mất một chuỗi và trả về void. Điều này tương tự như một người tiêu dùng. Bạn chỉ đang cung cấp một triển khai phương thức đó dưới dạng một người tiêu dùng tương tự như việc triển khai lớp bên trong ẩn danh sau đây.

SayHello sh = new SayHello() {
    @Override
    public void speak(String message) {
        System.out.println("Hello " + message);
    }
};

names.forEach(n -> sh.speak(n));

May mắn thay, giao diện của bạn có một phương thức duy nhất sao cho hệ thống loại (chính thức hơn là thuật toán phân giải loại) có thể suy ra kiểu của nó là SayHello. Nhưng nếu nó có 2 phương pháp trở lên, điều này sẽ không thể thực hiện được.

Tuy nhiên, một cách tiếp cận tốt hơn nhiều là khai báo người tiêu dùng trước vòng lặp for và sử dụng nó như hiển thị bên dưới. Tuyên bố việc thực hiện cho mỗi lần lặp lại tạo ra nhiều đối tượng hơn mức cần thiết và dường như phản trực giác với tôi. Đây là phiên bản nâng cao sử dụng tham chiếu phương thức thay vì lambda. Tham chiếu phương thức giới hạn được sử dụng ở đây gọi phương thức có liên quan trên hellothể hiện được khai báo ở trên.

SayHello hello = message -> System.out.println("Hello " + message);
names.forEach(hello::speak);

Cập nhật

Cho rằng đối với lambdas không trạng thái không nắm bắt được bất cứ điều gì từ phạm vi từ vựng của chúng chỉ một lần sẽ được tạo ra, cả hai cách tiếp cận chỉ tạo ra một thể hiện của SayHello, và không có bất kỳ lợi ích nào theo cách tiếp cận được đề xuất. Tuy nhiên, đây dường như là một chi tiết triển khai và tôi đã không biết đến nó cho đến bây giờ. Vì vậy, một cách tiếp cận tốt hơn nhiều chỉ là chuyển người tiêu dùng đến forEach của bạn như được đề xuất trong bình luận bên dưới. Cũng lưu ý rằng tất cả các cách tiếp cận này chỉ tạo ra một phiên bản của SayHellogiao diện của bạn trong khi cách tiếp cận cuối cùng ngắn gọn hơn. Đây là vẻ ngoài của nó.

names.forEach(message -> System.out.println("Hello " + message));

Đây câu trả lời sẽ cung cấp cho bạn cái nhìn sâu sắc hơn về điều đó. Đây là phần có liên quan từ JLS §15.27.4 : Đánh giá thời gian thực của biểu thức Lambda

Các quy tắc này nhằm cung cấp tính linh hoạt cho việc triển khai ngôn ngữ lập trình Java, theo đó:

  • Một đối tượng mới không cần phải được phân bổ trên mỗi đánh giá.

Trong thực tế, ban đầu tôi nghĩ mọi đánh giá đều tạo ra một trường hợp mới, đó là sai. @Holger cảm ơn đã chỉ ra rằng, bắt tốt.


Ok, tôi nghĩ rằng tôi hiểu. Tôi chỉ đơn giản là tạo một phiên bản của giao diện Người tiêu dùng. Nhưng, vì tôi đặt nó vào vòng lặp, tôi đã tạo một đối tượng riêng biệt trên mỗi vòng lặp, ăn hết bộ nhớ. Trong khi đó, việc triển khai của bạn tạo ra một đối tượng đó và hoán đổi "thông điệp" trên mỗi vòng lặp. Cám ơn vì đã giải thích!
Brodiman

1
Có, sẽ luôn tốt nếu bạn có thể sử dụng giao diện chức năng hiện có thay vì tự triển khai. Trong trường hợp này, bạn có thể sử dụng Consumer<String>thay vì có sayHellogiao diện.
Ravindra Ranwala

1
Không quan trọng bạn đặt dòng ở đâu SayHello hello = message -> System.out.println("Hello " + message);, sẽ chỉ có một SayHellotrường hợp. Dù rằng dù chỉ một đối tượng đã lỗi thời ở đây, vì điều tương tự cũng có thể đạt được thông qua names.forEach(n -> System.out.println("Hello " + n)), không có sự cải thiện nào trong phiên bản nâng cao của bạn.
Holger

@Holger Tôi đã cập nhật câu trả lời với đề nghị của bạn. Thực sự đánh giá cao nó. Vì vậy, tất cả các triển khai tạo ra một phiên bản SayHello duy nhất trong khi phiên bản do bạn đề xuất nhỏ gọn hơn? Có phải vậy không?
Ravindra Ranwala

1

Chữ ký của foreach là rất giống như dưới đây:

void forEach(Consumer<? super T> action)

Nó có một đối tượng của người tiêu dùng. Giao diện người tiêu dùng là giao diện chức năng (giao diện với một phương thức trừu tượng duy nhất). Nó chấp nhận một đầu vào và trả về không có kết quả.

Đây là định nghĩa:

@FunctionalInterface
public interface Consumer {
    void accept(T t);
}

Do đó, bất kỳ triển khai nào, một trong trường hợp của bạn có thể được viết theo hai cách:

Consumer<String> printConsumer = new Consumer<String>() {
    public void accept(String name) {
        SayHello hello = (message) -> System.out.println("Hello " + message);
        hello.speak(n);
    };
};

HOẶC LÀ

(n) -> {
            SayHello hello = (message) -> System.out.println("Hello " + message);
            hello.speak(n);
        }

Tương tự mã cho lớp SayHello có thể được viết là

SayHello sh = new SayHello() {
    @Override
    public void speak(String message) {
        System.out.println("Hello " + message);
    }
};

HOẶC LÀ

SayHello hello = message -> System.out.println("Hello " + message);

Vì thế names.foreach trước tiên bên trong gọi phương thức Consumer.accept và chạy triển khai lambda / nặc danh của nó để tạo và gọi triển khai lambda SayHello, v.v. Sử dụng biểu thức lambda mã rất nhỏ gọn và rõ ràng. Điều duy nhất bạn cần hiểu là nó hoạt động như thế nào, trong trường hợp của bạn, đó là sử dụng giao diện Người tiêu dùng.

Vì vậy, dòng chảy cuối cùng: foreach -> consumer.accept -> sayhello.speak

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.