Apa perbedaan antara Callable <T> dan Pemasok Java 8 <T>?

13

Saya telah beralih ke Java dari C # setelah beberapa rekomendasi dari beberapa di CodeReview. Jadi, ketika saya melihat ke LWJGL, satu hal yang saya ingat adalah bahwa setiap panggilan Displayharus dieksekusi pada utas yang sama dengan Display.create()metode yang digunakan. Mengingat ini, saya menyiapkan kelas yang terlihat seperti ini.

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());
    }
}

Saat menulis kelas ini Anda akan melihat bahwa saya membuat metode yang disebut isClosed()yang mengembalikan a Future<Boolean>. Ini kiriman fungsi untuk saya Schedulerantarmuka (yang tidak lebih dari pembungkus di sekitar ScheduledExecutorService. Sementara menulis schedulemetode pada Scheduleraku menyadari bahwa aku baik bisa menggunakan Supplier<T>argumen atau sebuah Callable<T>argumen untuk mewakili fungsi yang dilewatkan dalam. ScheduledExecutorServiceTidak mengandung ganti untuk Supplier<T>tetapi saya perhatikan bahwa ekspresi lambda () -> Display.isCloseRequested()sebenarnya adalah tipe yang kompatibel dengan keduanya Callable<bool> dan Supplier<bool> .

Pertanyaan saya adalah, apakah ada perbedaan antara keduanya, secara semantik atau sebaliknya - dan jika demikian, apa itu, sehingga saya dapat mematuhinya?

Dan Pantry
sumber
Saya berada di bawah kode tayangan yang tidak berfungsi = SO, kode yang berfungsi tetapi perlu ditinjau = CodeReview, pertanyaan umum yang mungkin atau mungkin tidak memerlukan kode = programmer. Kode saya benar-benar berfungsi dan hanya ada sebagai contoh. Saya juga tidak meminta ulasan, hanya bertanya tentang semantik.
Dan Pantry
..tanya tentang semantik sesuatu bukanlah pertanyaan konseptual?
Dan Pantry
Saya pikir ini adalah pertanyaan konseptual, bukan sebagai konseptual seperti pertanyaan bagus lainnya di situs ini tetapi ini bukan tentang implementasi. Kode berfungsi, pertanyaannya bukan tentang kode. Pertanyaannya adalah "apa perbedaan antara kedua antarmuka ini?"
Mengapa Anda ingin beralih dari C # ke Java!
Didier A.
2
Ada satu perbedaan, yaitu Callable.call () melempar pengecualian dan Supplier.get () tidak. Itu membuat yang terakhir jauh lebih menarik dalam ekspresi lambda.
Thorbjørn Ravn Andersen

Jawaban:

6

Jawaban singkatnya adalah keduanya menggunakan antarmuka fungsional, tetapi patut juga dicatat bahwa tidak semua antarmuka fungsional harus memiliki @FunctionalInterfaceanotasi. Bagian penting dari JavaDoc berbunyi:

Namun, kompiler akan memperlakukan setiap antarmuka yang memenuhi definisi antarmuka fungsional sebagai antarmuka fungsional terlepas dari apakah anotasi FunctionalInterface hadir atau tidak pada deklarasi antarmuka.

Dan definisi paling sederhana dari antarmuka fungsional adalah (hanya, tanpa pengecualian lain) hanya:

Secara konseptual, antarmuka fungsional memiliki tepat satu metode abstrak.

Oleh karena itu, dalam jawaban @Maciej Chalapuk , dimungkinkan juga untuk menjatuhkan anotasi dan menentukan lambda yang diinginkan:

// 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

Sekarang, apa yang membuat keduanya Callabledan Supplierantarmuka fungsional adalah karena mereka benar-benar mengandung satu metode abstrak:

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

Karena kedua metode tidak mengambil argumen (sebagai lawan dari MyInterface.myCall(int)contoh), parameter formal kosong ( ()).

Saya perhatikan bahwa ekspresi lambda () -> Display.isCloseRequested()sebenarnya adalah tipe yang kompatibel dengan keduanya Callable<Boolean> dan Supplier<Boolean> .

Seperti yang seharusnya Anda dapat simpulkan sekarang, itu hanya karena kedua metode abstrak akan mengembalikan jenis ekspresi yang Anda gunakan. Anda pasti harus menggunakan yang Callablediberikan penggunaan a ScheduledExecutorService.

Eksplorasi lebih lanjut (di luar cakupan pertanyaan)

Kedua antarmuka berasal dari paket yang sama sekali berbeda , karenanya mereka digunakan secara berbeda juga. Dalam kasus Anda, saya tidak melihat bagaimana implementasi akan digunakan, kecuali jika memasok :Supplier<T>Callable

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

Yang pertama () ->dapat secara longgar ditafsirkan sebagai " Suppliermemberi ..." dan yang kedua sebagai " Callablememberi ...". return value;adalah tubuh Callablelambda, yang dengan sendirinya adalah tubuh Supplierlambda.

Namun, penggunaan dalam contoh yang dibuat ini menjadi sedikit rumit, karena Anda sekarang harus get()dari yang Supplierpertama sebelum get()mengambil hasil Anda dari Future, yang pada gilirannya akan membuat call()Anda tidak Callablesinkron.

public static <T> T doWork(Supplier<Callable<T>> callableSupplier) {
    // service being an instance of ExecutorService
    return service.submit(callableSupplier.get()).get();
}
hjk
sumber
1
Saya mengalihkan jawaban yang diterima ke jawaban ini karena ini hanya jauh lebih komprehensif
Dan Pantry
Lebih lama tidak berarti lebih berguna, lihat jawaban @ srrm_lwn.
SensorSmith
@SensorSmith jawaban srrms adalah yang asli yang saya tandai sebagai jawaban yang diterima. Saya masih berpikir yang satu ini lebih bermanfaat.
Dan Pantry
21

Satu perbedaan mendasar antara kedua antarmuka adalah bahwa Callable memungkinkan pengecualian yang dicentang untuk dikeluarkan dari dalam pelaksanaannya, sementara Pemasok tidak.

Berikut ini cuplikan kode dari JDK yang menyoroti ini -

@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();
}
srrm_lwn
sumber
Ini membuat Callable tidak dapat digunakan sebagai argumen dalam antarmuka fungsional.
Basilevs
3
@ Basilev tidak, tidak - hanya tidak dapat digunakan di tempat yang mengharapkan Supplier, seperti aliran API. Anda benar-benar dapat lulus lambda dan referensi metode ke metode yang mengambil a Callable.
dimo414
12

Seperti yang Anda perhatikan, dalam praktiknya mereka melakukan hal yang sama (memberikan semacam nilai), namun pada prinsipnya mereka dimaksudkan untuk melakukan hal-hal yang berbeda:

A Callableadalah " Tugas yang mengembalikan hasil , sedangkan a Supplieradalah" pemasok hasil ". Dengan kata lain a Callableadalah cara untuk merujuk unit kerja yang belum berjalan, sedangkan a Supplieradalah cara untuk merujuk nilai yang belum diketahui.

Ada kemungkinan bahwa seorang Callablebisa melakukan pekerjaan yang sangat sedikit dan hanya mengembalikan nilai. Mungkin juga Supplierbisa melakukan banyak pekerjaan (misalnya membangun struktur data yang besar). Tetapi secara umum apa yang Anda pedulikan adalah tujuan prinsip mereka. Misalnya sebuah ExecutorServicekarya dengan Callables, karena tujuan utamanya adalah untuk mengeksekusi unit kerja. Sebuah menyimpan data malas-dimuat akan menggunakan Supplier, karena peduli tentang yang disediakan nilai, tanpa banyak kekhawatiran tentang berapa banyak pekerjaan yang mungkin mengambil.

Cara lain untuk mengutarakan perbedaannya adalah bahwa a Callablemungkin memiliki efek samping (misalnya menulis ke file), sementara a Supplierumumnya harus bebas efek samping. Dokumentasi tidak secara eksplisit menyebutkan ini (karena ini bukan keharusan ), tapi saya sarankan untuk berpikir dalam istilah-istilah itu. Jika karya idempoten gunakan a Supplier, jika tidak gunakan a Callable.

dimo414
sumber
2

Keduanya adalah antarmuka Java normal tanpa semantik khusus. Callable adalah bagian dari API bersamaan. Pemasok adalah bagian dari API pemrograman fungsional baru. Mereka dapat dibuat dari ekspresi lambda berkat perubahan di Java8. @FunctionalInterface membuat kompiler memeriksa bahwa antarmuka berfungsi dan meningkatkan kesalahan jika tidak, tetapi antarmuka tidak memerlukan penjelasan itu untuk menjadi antarmuka fungsional dan diimplementasikan oleh lambdas. Ini seperti bagaimana metode bisa menjadi override tanpa ditandai @Override tetapi tidak sebaliknya.

Anda dapat mendefinisikan antarmuka Anda sendiri yang kompatibel dengan lambdas dan mendokumentasikannya dengan @FunctionalInterfaceanotasi. Namun mendokumentasikan adalah opsional.

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

...

MyInterface var = (int a) -> a != 0;
Maciej Chałapuk
sumber
Meskipun patut dicatat antarmuka khusus ini disebut IntPredicatedi Jawa.
Konrad Borowski