Callable <T> और Java 8 के सप्लायर <T> में क्या अंतर है?


14

मैं CodeReview में कुछ से कुछ सिफारिशों के बाद जावा से C # पर स्विच कर रहा हूं। इसलिए, जब मैं 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>

मेरा प्रश्न यह है कि क्या उन दोनों में कोई अंतर है, शब्दार्थ रूप से या अन्यथा - और यदि हां, तो यह क्या है, इसलिए मैं इसका पालन कर सकता हूं?


मैं इंप्रेशन कोड के तहत काम नहीं कर रहा था = SO, कोड जो काम करता है, लेकिन समीक्षा की आवश्यकता है = CodeReview, सामान्य प्रश्न जिन्हें कोड = प्रोग्रामर की आवश्यकता हो सकती है या नहीं हो सकती है। मेरा कोड वास्तव में काम करता है और केवल एक उदाहरण के रूप में है। मैं समीक्षा के लिए नहीं कह रहा हूँ, या तो, केवल शब्दार्थ के बारे में पूछ रहा हूँ।
डैन पेंट्री

.. किसी चीज के शब्दार्थ के बारे में सोचना वैचारिक सवाल नहीं है?
डेन पेंट्री

मुझे लगता है कि यह एक वैचारिक प्रश्न है, इस साइट पर अन्य अच्छे प्रश्नों की तरह वैचारिक नहीं है, लेकिन यह कार्यान्वयन के बारे में नहीं है। कोड काम करता है, सवाल कोड के बारे में नहीं है। सवाल पूछ रहा है "इन दो इंटरफेस के बीच अंतर क्या है?"

आप C # से जावा क्यों स्विच करना चाहेंगे!
डिडिएर ए।

2
एक अंतर है, जिसका नाम है कि Callable.call () फेंकता अपवाद और आपूर्तिकर्ता.गेट () नहीं है। यह लैम्ब्डा के भावों में उत्तरार्द्ध को और अधिक आकर्षक बनाता है।
थोरबजर्न रेवन एंडरसन

जवाबों:


6

संक्षिप्त उत्तर यह है कि दोनों कार्यात्मक इंटरफेस का उपयोग कर रहे हैं, लेकिन यह भी ध्यान देने योग्य है कि सभी कार्यात्मक इंटरफेस में @FunctionalInterfaceएनोटेशन नहीं होना चाहिए । JavaDoc का महत्वपूर्ण भाग पढ़ता है:

हालाँकि, कंपाइलर किसी भी इंटरफ़ेस को एक कार्यात्मक इंटरफ़ेस की परिभाषा को एक कार्यात्मक इंटरफ़ेस के रूप में मानता है, भले ही इंटरफ़ेस घोषणापत्र पर एक FunctionalInterface एनोटेशन मौजूद है या नहीं।

और एक कार्यात्मक इंटरफ़ेस की सबसे सरल परिभाषा है (बस, अन्य बहिष्करण के बिना) बस:

वैचारिक रूप से, एक कार्यात्मक इंटरफ़ेस में एक सार विधि होती है।

इसलिए, @ मिसीज चालपुक के उत्तर में, एनोटेशन को छोड़ना और वांछित लंबो को निर्दिष्ट करना भी संभव है:

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

जैसा कि आप अब तक पता लगाने में सक्षम होना चाहिए, यह सिर्फ इसलिए है क्योंकि दोनों अमूर्त तरीके आपके द्वारा उपयोग किए जाने वाले अभिव्यक्ति के प्रकार को वापस कर देंगे। आपको निश्चित रूप से Callableदिए गए अपने उपयोग का उपयोग करना चाहिए ScheduledExecutorService

आगे की खोज (प्रश्न के दायरे से परे)

दोनों इंटरफेस पूरी तरह से अलग-अलग पैकेज से आते हैं , इसलिए उनका उपयोग अलग-अलग तरीके से भी किया जाता है। आपके मामले में, मैं यह नहीं देखता कि Supplier<T>इसका उपयोग कैसे किया जाएगा, जब तक कि यह आपूर्ति न करे Callable:

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

पहली () ->को "एक Supplierदेता है ..." के रूप में और दूसरी "एक Callableदेता है ..." के रूप में व्याख्या की जा सकती है । मेमने return value;का शरीर है Callable, जो खुद मेमने का शरीर है Supplier

हालांकि, इस काल्पनिक उदाहरण में उपयोग थोड़ा जटिल हो जाता है के रूप में आप अब की जरूरत get()से Supplierपहले पहले get()से अपने परिणाम -ting 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
मैं इस उत्तर के लिए स्वीकृत उत्तर स्विच कर रहा हूं क्योंकि यह सिर्फ और अधिक व्यापक है
डैन पेंट्री

लंबे समय तक अधिक उपयोगी के बराबर नहीं है, @ srrm_lwn का जवाब देखें।
SensorSmith

@SensorSmith srrms उत्तर वह मूल था जिसे मैंने स्वीकृत उत्तर के रूप में चिह्नित किया था। मुझे अभी भी लगता है कि यह अधिक उपयोगी है।
डैन पेंट्री

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

यह कार्यात्मक इंटरफ़ेस में तर्क के रूप में कॉल करने योग्य अनुपयोगी बनाता है।
बसिलेव्स

3
@Basilevs नहीं, ऐसा नहीं है - यह उन स्थानों पर उपयोग करने योग्य नहीं है Supplier, जो स्ट्रीम एपीआई जैसी उम्मीद करते हैं । आप पूरी तरह से लैम्ब्डा और विधि संदर्भों को पारित कर सकते हैं जो ए लेते हैं Callable
dimo414

13

जैसा कि आप ध्यान दें, व्यवहार में वे एक ही काम करते हैं (कुछ प्रकार के मूल्य प्रदान करें), हालांकि सिद्धांत रूप में वे अलग-अलग काम करने के लिए अभिप्रेत हैं:

A Callable" एक कार्य है जो एक परिणाम देता है , जबकि एक Supplier" परिणामों का आपूर्तिकर्ता है । "दूसरे शब्दों में एक Callableकाम का एक अभी तक अनारुण इकाई Supplierको संदर्भित करने का एक तरीका है , जबकि एक अभी तक अज्ञात मूल्य का संदर्भ देने का एक तरीका है।

यह संभव है कि Callableबहुत कम काम कर सकता है और बस एक मूल्य वापस कर सकता है। यह भी संभव है Supplierकि बहुत काम कर सके (जैसे एक बड़ी डेटा संरचना का निर्माण)। लेकिन आम तौर पर बोलना कि आप किन बातों का ध्यान रखते हैं, उनका सिद्धांत उद्देश्य है। उदाहरण के लिए , एस के ExecutorServiceसाथ एक काम करता है Callable, क्योंकि यह प्राथमिक उद्देश्य काम की इकाइयों को निष्पादित करना है। एक आलसी-भारित डेटा स्टोर एक का उपयोग करेगा Supplier, क्योंकि यह एक मूल्य की आपूर्ति के बारे में परवाह करता है , बिना इस बात की चिंता किए कि कितना काम हो सकता है।

भेद को फिर से तैयार करने का एक और तरीका यह है कि Callableइसके साइड-इफेक्ट्स हो सकते हैं (उदाहरण के लिए किसी फाइल पर लिखना), जबकि Supplierआम तौर पर साइड-इफेक्ट फ्री होना चाहिए। प्रलेखन स्पष्ट रूप से इसका उल्लेख नहीं करता है (क्योंकि यह एक आवश्यकता नहीं है ), लेकिन मैं उन शब्दों में सोचने का सुझाव दूंगा। काम है idempotent एक का उपयोग Supplier, अगर एक का उपयोग नहीं Callable


2

दोनों सामान्य जावा इंटरफेस हैं जिनमें कोई विशेष शब्दार्थ नहीं है। कॉल करने योग्य समवर्ती एपीआई का एक हिस्सा है। आपूर्तिकर्ता नए कार्यात्मक प्रोग्रामिंग एपीआई का एक हिस्सा है। उन्हें Java8 में बदलाव के लिए लैंबडा एक्सप्रेशन से बनाया जा सकता है। @FunctionalInterface कंपाइलर की जाँच करता है कि इंटरफ़ेस कार्यात्मक है और यदि यह नहीं है तो एक त्रुटि उठाता है, लेकिन एक इंटरफ़ेस को एक कार्यात्मक इंटरफ़ेस होने और लैम्बडा द्वारा कार्यान्वित किए जाने के लिए उस एनोटेशन की आवश्यकता नहीं है। यह ऐसा है कि कैसे कोई विधि @ ओवरवेर के रूप में चिह्नित किए बिना ओवरराइड हो सकती है लेकिन इसके विपरीत नहीं।

आप लैम्ब्डा के साथ संगत अपने स्वयं के इंटरफेस को परिभाषित कर सकते हैं और उन्हें @FunctionalInterfaceएनोटेशन के साथ दस्तावेज कर सकते हैं। दस्तावेज़ीकरण हालांकि वैकल्पिक है।

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

...

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

हालांकि इस विशेष इंटरफ़ेस को ध्यान देने योग्य है जिसे IntPredicateजावा में कहा जाता है।
कोनराड बोरोस्की
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.