อะไรคือความแตกต่างระหว่าง Callable <T> และซัพพลายเออร์ของ Java 8 <T>?


14

ฉันเปลี่ยนมาใช้ Java จาก C # หลังจากได้รับคำแนะนำจาก CodeReview ดังนั้นเมื่อฉันมองไปที่ LWJGL สิ่งหนึ่งที่ฉันจำได้ก็คือทุกการเรียกจะDisplayต้องดำเนินการในเธรดเดียวกันกับDisplay.create()วิธีที่เรียกใช้ เมื่อนึกถึงสิ่งนี้ฉันได้สร้างคลาสที่มีลักษณะเช่นนี้ขึ้นมา

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

ในขณะที่เขียนระดับนี้คุณจะสังเกตเห็นว่าฉันสร้างวิธีการที่เรียกว่าผลตอบแทนisClosed() Future<Boolean>ยื้อนี้ฟังก์ชั่นของฉันSchedulerอินเตอร์เฟซ (ซึ่งเป็นอะไรมากไปกว่าห่อหุ้มรอบยScheduledExecutorService. ในขณะที่เขียนscheduleวิธีการเกี่ยวกับการSchedulerที่ผมสังเกตเห็นว่าผมทั้งสามารถใช้Supplier<T>การโต้แย้งหรือCallable<T>โต้แย้งเพื่อเป็นตัวแทนของฟังก์ชั่นที่ถูกส่งผ่านใน. ScheduledExecutorServiceไม่ได้มี แทนที่สำหรับSupplier<T>แต่ผมสังเกตเห็นว่าการแสดงออกแลมบ์ดา() -> Display.isCloseRequested()เป็นจริงพิมพ์เข้ากันได้กับทั้งสองและCallable<bool> Supplier<bool>

คำถามของฉันคือมีความแตกต่างระหว่างสองคนนี้มีความหมายหรืออย่างอื่น - และถ้าเป็นเช่นนั้นมันคืออะไรดังนั้นฉันสามารถปฏิบัติตามมันได้หรือไม่


ฉันอยู่ภายใต้รหัสการแสดงผลที่ไม่ทำงาน = ดังนั้นรหัสที่ใช้งานได้ แต่ต้องมีการตรวจสอบ = CodeReview คำถามทั่วไปที่อาจหรือไม่จำเป็นต้องใช้รหัส = โปรแกรมเมอร์ รหัสของฉันใช้งานได้จริงและมีเพียงตัวอย่างเท่านั้น ฉันไม่ได้ขอความเห็นเช่นกันแค่ถามเกี่ยวกับความหมาย
Dan Pantry

.. การนึกถึงความหมายของบางสิ่งไม่ใช่คำถามเชิงแนวคิด
Dan Pantry

ฉันคิดว่านี่เป็นคำถามเกี่ยวกับแนวคิดไม่ใช่เป็นแนวคิดที่เป็นคำถามที่ดีในเว็บไซต์นี้ แต่ไม่เกี่ยวกับการใช้งาน รหัสใช้งานได้คำถามไม่เกี่ยวกับรหัส คำถามจะถามว่า "อะไรคือความแตกต่างระหว่างสองอินเตอร์เฟสนี้"

ทำไมคุณต้องการเปลี่ยนจาก C # เป็น Java!
Didier A.

2
มีข้อแตกต่างประการหนึ่งคือ Callable.call () แสดงข้อยกเว้นและ Supplier.get () ไม่ได้ นั่นทำให้ภาพหลังแลมบ์ดามีเสน่ห์มากขึ้น
Thorbjørn Ravn Andersen

คำตอบ:


6

คำตอบสั้น ๆ คือทั้งคู่กำลังใช้อินเทอร์เฟซการทำงาน แต่ก็ควรที่จะทราบว่าอินเทอร์เฟซที่ใช้งานไม่ได้ทั้งหมดต้องมี@FunctionalInterfaceหมายเหตุ ส่วนที่สำคัญของ JavaDoc อ่าน:

อย่างไรก็ตามคอมไพลเลอร์จะจัดการกับอินเตอร์เฟสใด ๆ ที่นิยามคำสั่งของอินเตอร์เฟสการทำงานเป็นอินเตอร์เฟสการทำงานโดยไม่คำนึงว่าหมายเหตุประกอบ FunctionInterface นั้นมีอยู่หรือไม่ในการประกาศอินเตอร์เฟส

และคำจำกัดความที่ง่ายที่สุดของส่วนต่อประสานการทำงานก็คือ (โดยไม่มีข้อยกเว้นอื่น ๆ ) เพียง:

ตามแนวคิดแล้วส่วนต่อประสานการทำงานมีวิธีนามธรรมเพียงวิธีเดียว

ดังนั้นในคำตอบของ @Maciej Chalapukก็เป็นไปได้ที่จะวางคำอธิบายประกอบและระบุแลมบ์ดาที่ต้องการ:

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

ตอนนี้สิ่งที่ทำให้ทั้งสองCallableและSupplierส่วนต่อประสานการทำงานนั้นเป็นเพราะพวกเขามีวิธีนามธรรมที่แน่นอน:

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

เนื่องจากทั้งสองวิธีไม่ได้ใช้ในการโต้แย้ง (ตรงข้ามกับMyInterface.myCall(int)ตัวอย่าง) พารามิเตอร์ทางการจึงว่างเปล่า ( ())

ผมสังเกตเห็นว่าการแสดงออกแลมบ์ดา() -> Display.isCloseRequested()เป็นจริงพิมพ์เข้ากันได้กับทั้งสองและCallable<Boolean> Supplier<Boolean>

ตามที่คุณควรจะสามารถอนุมานได้ตอนนี้เป็นเพียงเพราะวิธีนามธรรมทั้งสองจะกลับประเภทของการแสดงออกที่คุณใช้ คุณแน่นอนควรจะใช้ให้ใช้งานของคุณของCallableScheduledExecutorService

การสำรวจเพิ่มเติม (เกินขอบเขตของคำถาม)

อินเตอร์เฟซทั้งสองมาจากทั้งหมดที่แตกต่างกัน แพคเกจดังนั้นพวกเขาจะใช้แตกต่างกันเกินไป ในกรณีของคุณฉันไม่เห็นว่าSupplier<T>จะมีการนำไปใช้อย่างไรเว้นแต่จะมีการส่งมอบCallable:

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

คนแรก() ->สามารถตีความได้อย่างหลวม ๆ ว่า "คนSupplierให้ ... " และคนที่สองเป็น "คนCallableให้ ... " return value;เป็นร่างของCallableแลมบ์ดาซึ่งเป็นร่างของSupplierแลมบ์ดา

อย่างไรก็ตามการใช้งานในตัวอย่างที่ประดิษฐ์นี้มีความซับซ้อนเล็กน้อยเนื่องจากตอนนี้คุณจำเป็นต้องใช้get()ตั้งแต่Supplierแรกก่อนที่จะนำget()ผลลัพธ์ของคุณจากFutureซึ่งจะทำให้call()คุณCallableไม่ตรงกัน

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

1
ฉันเปลี่ยนคำตอบที่ยอมรับสำหรับคำตอบนี้เพราะมันเป็นเพียงที่ครอบคลุมมากขึ้น
Dan Pantry

อีกต่อไปไม่ถือเอาประโยชน์ที่มีมากกว่าดูคำตอบของ @ srrm_lwn
SensorSmith

@SensorSmith srrms คำตอบคือคำตอบเดิมที่ฉันทำเครื่องหมายว่าเป็นคำตอบที่ยอมรับแล้ว ฉันยังคิดว่าอันนี้มีประโยชน์มากกว่า
Dan Pantry

21

ความแตกต่างพื้นฐานอย่างหนึ่งระหว่าง 2 อินเตอร์เฟสคือ Callable ช่วยให้สามารถยกเว้นข้อยกเว้นที่ตรวจสอบได้จากการใช้งานส่วนนี้ในขณะที่ซัพพลายเออร์ไม่ได้ทำ

นี่คือตัวอย่างรหัสจาก JDK เน้นนี้ -

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

สิ่งนี้ทำให้ Callable ไม่สามารถใช้งานได้เป็นอาร์กิวเมนต์ในส่วนต่อประสานการทำงาน
Basilevs

3
@Basilev ไม่เป็นไร - มันใช้ไม่ได้ในสถานที่ที่คาดหวังSupplierเช่นa API สตรีม คุณอย่างสามารถผ่าน lambdas Callableและการอ้างอิงวิธีการเพื่อให้วิธีการที่ใช้
dimo414

13

ตามที่คุณทราบในทางปฏิบัติพวกเขาทำสิ่งเดียวกัน (ให้คุณค่าบางอย่าง) อย่างไรก็ตามในหลักการพวกเขาตั้งใจจะทำสิ่งต่าง ๆ :

A Callableคือ " งานที่ส่งคืนผลลัพธ์ในขณะที่ a Supplierคือ" ซัพพลายเออร์ของผลลัพธ์ "ในคำอื่น ๆ a Callableเป็นวิธีการอ้างอิงหน่วยงานที่ยังไม่ได้ใช้งานในขณะที่ a Supplierเป็นวิธีอ้างอิงค่าที่ยังไม่ทราบ

เป็นไปได้ว่า a Callableสามารถทำงานได้น้อยมากและเพียงคืนค่า นอกจากนี้ยังเป็นไปได้ที่ a Supplierสามารถทำงานค่อนข้างมาก (เช่นสร้างโครงสร้างข้อมูลขนาดใหญ่) แต่โดยทั่วไปแล้วการพูดในสิ่งที่คุณสนใจก็คือจุดประสงค์หลักของพวกเขา ตัวอย่างเช่นการExecutorServiceทำงานกับCallables เพราะมันมีวัตถุประสงค์หลักคือการดำเนินการหน่วยงาน แหล่งข้อมูลที่โหลดขี้เกียจจะใช้ a Supplierเพราะมันใส่ใจกับการให้ค่าโดยไม่ต้องกังวลว่าจะต้องใช้งานมากแค่ไหน

อีกวิธีในการใช้ถ้อยคำที่แตกต่างคือ a Callableอาจมีผลข้างเคียง (เช่นการเขียนไปยังไฟล์) ในขณะที่ a Supplierโดยทั่วไปควรเป็นผลข้างเคียงที่เป็นอิสระ เอกสารไม่ได้กล่าวถึงเรื่องนี้อย่างชัดเจน (เนื่องจากไม่ใช่ข้อกำหนด ) แต่ฉันขอแนะนำให้คิดในแง่เหล่านั้น ถ้าทำงานเป็นidempotentใช้หากไม่ได้ใช้SupplierCallable


2

ทั้งสองเป็นอินเทอร์เฟซ Java ปกติโดยไม่มีความหมายพิเศษ Callableเป็นส่วนหนึ่งของ API ที่เกิดขึ้นพร้อมกัน ผู้จัดหาเป็นส่วนหนึ่งของ API การเขียนโปรแกรมการทำงานใหม่ พวกเขาสามารถสร้างขึ้นจากการแสดงออกแลมบ์ดาขอบคุณการเปลี่ยนแปลงใน Java8 @FunctionalInterfaceทำให้คอมไพเลอร์ตรวจสอบว่าอินเตอร์เฟสทำงานและเพิ่มข้อผิดพลาดหากไม่ได้ แต่อินเตอร์เฟสไม่จำเป็นต้องใช้คำอธิบายประกอบนั้นเป็นส่วนต่อประสานการทำงานและใช้งานโดย lambdas นี่เป็นวิธีที่วิธีการสามารถแทนที่โดยไม่ต้องทำเครื่องหมาย @Override แต่ไม่กลับกัน

คุณสามารถกำหนดอินเทอร์เฟซของคุณเองที่เข้ากันได้กับ lambdas และจัดทำเอกสารด้วย@FunctionalInterfaceหมายเหตุประกอบ การทำเอกสารเป็นทางเลือก

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

...

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

แม้ว่าจะสังเกตได้ว่าอินเทอร์เฟซนี้เรียกว่าIntPredicateใน Java
Konrad Borowski
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.