Sự khác biệt giữa <T> và Nhà cung cấp của Java 8 <T> là gì?


13

Tôi đã chuyển sang Java từ C # sau một số đề xuất từ ​​một số tại CodeReview. Vì vậy, khi tôi đang tìm hiểu về LWJGL, một điều tôi nhớ là mọi lệnh gọi Displayphải được thực hiện trên cùng một luồng mà Display.create()phương thức được gọi. Nhớ điều này, tôi đã đánh một lớp trông giống như thế này.

public class LwjglDisplayWindow implements DisplayWindow {
    private final static int TargetFramesPerSecond = 60;
    private final Scheduler _scheduler;

    public LwjglDisplayWindow(Scheduler displayScheduler, DisplayMode displayMode) throws LWJGLException {
        _scheduler = displayScheduler;
        Display.setDisplayMode(displayMode);
        Display.create();
    }

    public void dispose() {
        Display.destroy();
    }

    @Override
    public int getTargetFramesPerSecond() { return TargetFramesPerSecond; }

    @Override
    public Future<Boolean> isClosed() {
        return _scheduler.schedule(() -> Display.isCloseRequested());
    }
}

Trong khi viết lớp này, bạn sẽ nhận thấy rằng tôi đã tạo ra một phương pháp gọi isClosed()là lợi nhuận một Future<Boolean>. Điều này gửi một hàm đến Schedulergiao diện của tôi (không gì khác hơn là một trình bao bọc xung quanh ScheduledExecutorService. Trong khi viết schedulephương thức trên Schedulertôi nhận thấy rằng tôi có thể sử dụng một Supplier<T>đối số hoặc một Callable<T>đối số để biểu diễn hàm được truyền vào. ScheduledExecutorServiceKhông chứa ghi đè cho Supplier<T>nhưng tôi nhận thấy rằng biểu thức lambda () -> Display.isCloseRequested()thực sự là loại tương thích với cả Callable<bool> Supplier<bool> .

Câu hỏi của tôi là, có sự khác biệt giữa hai thứ đó, về mặt ngữ nghĩa hay mặt khác - và nếu vậy, nó là gì, để tôi có thể tuân thủ nó?


Tôi đã ở dưới mã hiển thị không hoạt động = SO, mã hoạt động nhưng cần xem lại = CodeReview, các câu hỏi chung có thể hoặc không cần mã = ​​lập trình viên. Mã của tôi thực sự hoạt động và chỉ là một ví dụ. Tôi cũng không yêu cầu đánh giá, chỉ hỏi về ngữ nghĩa.
Dan Pantry

.. nói về ngữ nghĩa của một cái gì đó không phải là một câu hỏi khái niệm?
Dan Pantry

Tôi nghĩ rằng đây là một câu hỏi về khái niệm, không phải là khái niệm như các câu hỏi hay khác trên trang web này nhưng nó không phải là về việc thực hiện. Mã hoạt động, câu hỏi không phải là về mã. Câu hỏi đặt ra là "sự khác biệt giữa hai giao diện này là gì?"

Tại sao bạn muốn chuyển từ C # sang Java!
Didier A.

2
Có một sự khác biệt, đó là Callable.call () đưa ra các ngoại lệ và Nhà cung cấp.get () không. Điều đó làm cho cái sau hấp dẫn hơn nhiều trong các biểu thức lambda.
Thorbjørn Ravn Andersen

Câu trả lời:


6

Câu trả lời ngắn gọn là cả hai đều đang sử dụng các giao diện chức năng, nhưng cũng đáng lưu ý rằng không phải tất cả các giao diện chức năng đều phải có @FunctionalInterfacechú thích. Phần quan trọng của JavaDoc đọc:

Tuy nhiên, trình biên dịch sẽ coi bất kỳ giao diện nào đáp ứng định nghĩa của giao diện chức năng là giao diện chức năng bất kể chú thích FunctionalInterface có xuất hiện trên khai báo giao diện hay không.

Và định nghĩa đơn giản nhất của giao diện chức năng là (đơn giản, không có loại trừ khác) chỉ:

Về mặt khái niệm, một giao diện chức năng có chính xác một phương thức trừu tượng.

Do đó, trong câu trả lời của @Maciej Chalapuk , cũng có thể bỏ chú thích và chỉ định lambda mong muốn:

// interface
public interface MyInterface {
    boolean myCall(int arg);
}

// method call
public boolean invokeMyCall(MyInterface arg) {
    return arg.myCall(0);
}

// usage
instance.invokeMyCall(a -> a != 0); // returns true if the argument supplied is not 0

Bây giờ, những gì làm cho cả hai CallableSuppliergiao diện chức năng là bởi vì chúng có chứa chính xác một phương thức trừu tượng:

  • Callable.call()
  • Supplier.get()

Vì cả hai phương thức không tham gia vào một đối số (trái ngược với MyInterface.myCall(int)ví dụ), các tham số chính thức là rỗng ( ()).

Tôi nhận thấy rằng biểu thức lambda () -> Display.isCloseRequested()thực sự là loại tương thích với cả Callable<Boolean> Supplier<Boolean> .

Bây giờ bạn có thể suy ra, đó chỉ là vì cả hai phương thức trừu tượng sẽ trả về kiểu biểu thức bạn sử dụng. Bạn chắc chắn nên sử dụng một Callablecách sử dụng a ScheduledExecutorService.

Thăm dò thêm (ngoài phạm vi câu hỏi)

Cả hai giao diện đến từ các gói hoàn toàn khác nhau , do đó chúng cũng được sử dụng khác nhau. Trong trường hợp của bạn, tôi không thấy cách triển khai Supplier<T>sẽ được sử dụng, trừ khi nó cung cấp Callable:

public static <T> Supplier<Callable<T>> getCallable(T value) {
    return () -> () -> {
        return value;
    };
}

Cái đầu tiên () ->có thể được hiểu một cách lỏng lẻo là " Suppliercho ..." và thứ hai là " Callablecho ...". return value;là cơ thể của Callablelambda, mà chính nó là cơ thể của Supplierlambda.

Tuy nhiên, việc sử dụng trong ví dụ giả tạo này được một chút phức tạp, như bây giờ bạn cần phải get()từ Supplierđầu tiên trước khi get()-ting kết quả của bạn từ Future, mà trong sẽ biến call()bạn Callablekhông đồng bộ.

public static <T> T doWork(Supplier<Callable<T>> callableSupplier) {
    // service being an instance of ExecutorService
    return service.submit(callableSupplier.get()).get();
}

1
Tôi đang chuyển đổi câu trả lời được chấp nhận cho câu trả lời này bởi vì đây chỉ đơn giản là toàn diện hơn nhiều
Dan Pantry

Lâu hơn không tương đương với hữu ích hơn, xem câu trả lời của @ srrm_lwn.
SensorSmith

Câu trả lời srrms của @SensorSmith là câu trả lời ban đầu tôi đánh dấu là câu trả lời được chấp nhận. Tôi vẫn nghĩ rằng cái này hữu ích hơn.
Dan Pantry

21

Một điểm khác biệt cơ bản giữa 2 giao diện là Callable cho phép các ngoại lệ được kiểm tra được đưa ra từ bên trong việc triển khai giao diện, trong khi Nhà cung cấp thì không.

Dưới đây là các đoạn mã từ JDK làm nổi bật điều này -

@FunctionalInterface
public interface Callable<V> {
/**
 * Computes a result, or throws an exception if unable to do so.
 *
 * @return computed result
 * @throws Exception if unable to compute a result
 */
V call() throws Exception;
}

@FunctionalInterface
public interface Supplier<T> {

/**
 * Gets a result.
 *
 * @return a result
 */
T get();
}

Điều này làm cho Callable không thể sử dụng làm đối số trong các giao diện chức năng.
Basilevs

3
@Basilevs không, không - không thể sử dụng được ở những nơi mong đợi Supplier, chẳng hạn như API luồng. Bạn hoàn toàn có thể truyền lambdas và tham chiếu phương thức cho các phương thức mất một Callable.
dimo414

12

Như bạn lưu ý, trong thực tế, họ làm điều tương tự (cung cấp một số loại giá trị), tuy nhiên về nguyên tắc họ dự định làm những việc khác nhau:

A Callablelà " Một tác vụ trả về kết quả , trong khi a Supplierlà" nhà cung cấp kết quả ". Nói cách khác, a Callablelà một cách để tham chiếu một đơn vị công việc chưa hoàn thành, trong khi đó Supplierlà một cách để tham chiếu một giá trị chưa biết.

Có thể là một công việc Callablecó thể làm rất ít và chỉ cần trả về một giá trị. Cũng có khả năng Suppliercó thể thực hiện khá nhiều công việc (ví dụ: xây dựng cấu trúc dữ liệu lớn). Nhưng nói chung, những gì bạn quan tâm với một trong hai là mục đích chính của họ. Ví dụ, một ExecutorServicetác phẩm có Callables, vì mục đích chính của nó là thực thi các đơn vị công việc. Một cửa hàng dữ liệu được tải lười biếng sẽ sử dụng một Supplier, bởi vì nó quan tâm đến việc được cung cấp một giá trị, mà không cần quan tâm nhiều đến việc có thể mất bao nhiêu công việc.

Một cách khác để phân biệt sự khác biệt là Callablecó thể có tác dụng phụ (ví dụ như ghi vào tệp), trong khi Suppliernói chung nên không có tác dụng phụ. Tài liệu không đề cập rõ ràng về điều này (vì nó không phải là một yêu cầu ), nhưng tôi khuyên bạn nên suy nghĩ theo các điều khoản đó. Nếu công việc là idempotent sử dụng một Supplier, nếu không sử dụng một Callable.


2

Cả hai đều là các giao diện Java bình thường không có ngữ nghĩa đặc biệt. Callable là một phần của API đồng thời. Nhà cung cấp là một phần của API lập trình chức năng mới. Chúng có thể được tạo từ các biểu thức lambda nhờ những thay đổi trong Java8. @F rốialInterface làm cho trình biên dịch kiểm tra xem giao diện có hoạt động không và có lỗi nếu không, nhưng một giao diện không cần chú thích đó là giao diện chức năng và được lambdas triển khai. Điều này giống như cách một phương thức có thể là ghi đè mà không được đánh dấu @Override nhưng không phải ngược lại.

Bạn có thể xác định các giao diện của riêng mình tương thích với lambdas và ghi lại chúng bằng @FunctionalInterfacechú thích. Tài liệu là tùy chọn mặc dù.

@FunctionalInterface
public interface MyInterface {
    boolean myCall(int arg);
}

...

MyInterface var = (int a) -> a != 0;

Mặc dù đáng chú ý giao diện cụ thể này được gọi IntPredicatetrong Java.
Konrad Borowski
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.