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 Display
harus 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 Scheduler
antarmuka (yang tidak lebih dari pembungkus di sekitar ScheduledExecutorService
. Sementara menulis schedule
metode pada Scheduler
aku menyadari bahwa aku baik bisa menggunakan Supplier<T>
argumen atau sebuah Callable<T>
argumen untuk mewakili fungsi yang dilewatkan dalam. ScheduledExecutorService
Tidak 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?
Jawaban:
Jawaban singkatnya adalah keduanya menggunakan antarmuka fungsional, tetapi patut juga dicatat bahwa tidak semua antarmuka fungsional harus memiliki
@FunctionalInterface
anotasi. Bagian penting dari JavaDoc berbunyi:Dan definisi paling sederhana dari antarmuka fungsional adalah (hanya, tanpa pengecualian lain) hanya:
Oleh karena itu, dalam jawaban @Maciej Chalapuk , dimungkinkan juga untuk menjatuhkan anotasi dan menentukan lambda yang diinginkan:
Sekarang, apa yang membuat keduanya
Callable
danSupplier
antarmuka 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 (()
).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
Callable
diberikan penggunaan aScheduledExecutorService
.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
Yang pertama
() ->
dapat secara longgar ditafsirkan sebagai "Supplier
memberi ..." dan yang kedua sebagai "Callable
memberi ...".return value;
adalah tubuhCallable
lambda, yang dengan sendirinya adalah tubuhSupplier
lambda.Namun, penggunaan dalam contoh yang dibuat ini menjadi sedikit rumit, karena Anda sekarang harus
get()
dari yangSupplier
pertama sebelumget()
mengambil hasil Anda dariFuture
, yang pada gilirannya akan membuatcall()
Anda tidakCallable
sinkron.sumber
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 -
sumber
Supplier
, seperti aliran API. Anda benar-benar dapat lulus lambda dan referensi metode ke metode yang mengambil aCallable
.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
Callable
adalah " Tugas yang mengembalikan hasil , sedangkan aSupplier
adalah" pemasok hasil ". Dengan kata lain aCallable
adalah cara untuk merujuk unit kerja yang belum berjalan, sedangkan aSupplier
adalah cara untuk merujuk nilai yang belum diketahui.Ada kemungkinan bahwa seorang
Callable
bisa melakukan pekerjaan yang sangat sedikit dan hanya mengembalikan nilai. Mungkin jugaSupplier
bisa melakukan banyak pekerjaan (misalnya membangun struktur data yang besar). Tetapi secara umum apa yang Anda pedulikan adalah tujuan prinsip mereka. Misalnya sebuahExecutorService
karya denganCallable
s, karena tujuan utamanya adalah untuk mengeksekusi unit kerja. Sebuah menyimpan data malas-dimuat akan menggunakanSupplier
, karena peduli tentang yang disediakan nilai, tanpa banyak kekhawatiran tentang berapa banyak pekerjaan yang mungkin mengambil.Cara lain untuk mengutarakan perbedaannya adalah bahwa a
Callable
mungkin memiliki efek samping (misalnya menulis ke file), sementara aSupplier
umumnya 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 aSupplier
, jika tidak gunakan aCallable
.sumber
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
@FunctionalInterface
anotasi. Namun mendokumentasikan adalah opsional.sumber
IntPredicate
di Jawa.