Яка різниця між 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()a Future<Boolean>. Це відправляє функції на мій Schedulerінтерфейс (який є не більше ніж оболонка навколо ScheduledExecutorService. При написанні scheduleметоду на Schedulerя помітив , що я міг би або використовувати Supplier<T>аргумент або в Callable<T>аргумент для представлення функції, яка передається в. ScheduledExecutorServiceНе містять переосмислити, Supplier<T>але я помітив, що лямбда-вираз () -> Display.isCloseRequested()насправді є типом, сумісним і з Callable<bool> і Supplier<bool> .

Моє запитання: чи є різниця між цими двома, семантично чи інакше - і якщо так, то що це, то я можу дотримуватися цього?


Я опинився під кодом показів, який не працює = ТАК, код працює, але потребує перегляду = CodeReview, загальні питання, які можуть або не потребують коду = програмісти. Мій код насправді працює і є лише прикладом. Я також не прошу рецензування, просто запитую про семантику.
Ден Комора

..мовити про семантику чогось - це не концептуальне питання?
Dan Pantry

Я думаю, що це концептуальне питання, не настільки концептуальне, як інші хороші запитання на цьому сайті, але це не про реалізацію. Код працює, питання не в коді. Постає питання "в чому різниця між цими двома інтерфейсами?"

Чому ви хочете перейти з C # на Java!
Дідьє А.

2
Є одна відмінність, а саме те, що Callable.call () кидає винятки, а постачальник.get () - ні. Це робить останніх значно привабливішими в лямбдаських виразах.
Thorbjørn Ravn Andersen

Відповіді:


6

Коротка відповідь полягає в тому, що обидва використовують функціональні інтерфейси, але також варто зазначити, що не всі функціональні інтерфейси повинні мати @FunctionalInterfaceанотацію. Критична частина JavaDoc звучить:

Однак компілятор буде розглядати будь-який інтерфейс, що відповідає визначенню функціонального інтерфейсу, як функціональний інтерфейс незалежно від того, присутня примітка FunctionalInterface у декларації інтерфейсу чи ні.

А найпростішим визначенням функціонального інтерфейсу є (просто, без інших виключень) просто:

Концептуально функціональний інтерфейс має рівно один абстрактний метод.

Тому у відповіді @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> .

Як ви вже можете зробити висновок, це лише тому, що обидва абстрактні методи повернуть тип вираження, яке ви використовуєте. Ви обов'язково повинні використовувати 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()заздалегідь зафіксувати результат 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 відповідь був оригінальним, який я позначив як прийняту відповідь. Я все ще думаю, що ця корисніша.
Dan

21

Основна відмінність між двома інтерфейсами полягає в тому, що 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 непридатним як аргумент у функціональних інтерфейсах.
Василевс

3
@Basilevs ні, це не так - він просто не використовується в місцях, які очікують Supplier, наприклад, API потоку. Ви абсолютно можете передавати лямбда та посилання методів на методи, які використовують Callable.
dimo414

13

Як зазначаєте, на практиці вони роблять те саме (надають якусь цінність), однак в принципі вони покликані робити різні речі:

A Callable- це " завдання, яке повертає результат , тоді як a Supplier-" постачальник результатів ". Іншими словами, a Callable- це спосіб посилання на ще не вивчену одиницю роботи, а a Supplier- спосіб посилання на ще невідоме значення.

Цілком можливо, що a Callableможе зробити дуже мало роботи і просто повернути значення. Також можливо, що Supplierможе зробити досить багато роботи (наприклад, побудувати велику структуру даних). Але взагалі те, що вам важливо з будь-яким із них, - це їх основне призначення. Наприклад, ExecutorServiceробота з Callables, оскільки її основна мета - виконання одиниць роботи. Лінивий завантаженим сховище даних буде використовувати Supplier, тому що він піклується про те , подається значення, без особливого занепокоєння з приводу того , скільки роботи , які могли б прийняти.

Іншим способом формулювання розрізнення є те, що у Callableможе виникнути побічні ефекти (наприклад, запис у файл), в той час як a Supplierяк правило, не має побічних ефектів. У документації це прямо не згадується (оскільки це не є вимогою ), але я б запропонував подумати в цих умовах. Якщо робота є безсилою, використовуйте a Supplier, якщо не використовуйте a Callable.


2

Обидва - це звичайні інтерфейси Java без особливої ​​семантики. Callable - це частина паралельного API. Постачальник є частиною нового API функціонального програмування. Їх можна створити з лямбда-виразів завдяки змінам у Java8. @FunctionalInterface змушує компілятор перевірити, чи є інтерфейс функціональним, і створити помилку, якщо його немає, але інтерфейс не потребує того, щоб анотація була функціональним інтерфейсом і реалізовувалася лямбдами. Це подібно до того, як метод може бути переопрацьований без позначення @Override, але не навпаки.

Ви можете визначити власні інтерфейси, сумісні з лямбдами, і документувати їх з @FunctionalInterfaceанотацією. Документування, однак, необов’язково.

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

...

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

Хоча варто зазначити, що саме цей інтерфейс називається IntPredicateна Java.
Конрад Боровський
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.