Callable <T>とJava 8のSupplier <T>の違いは何ですか?


13

CodeReviewでのいくつかの推奨事項の後、C#からJavaに切り替えました。そのため、LWJGLを調べていたときに覚えていたことの1つは、への呼び出しはすべてDisplayDisplay.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の方法SchedulerIのいずれかを使用できることに気づいた私Supplier<T>の引数Callable<T>に渡される関数を表現するために、引数が。ScheduledExecutorService含まれていませんでしたオーバーライドしますSupplier<T>が、ラムダ式() -> Display.isCloseRequested()は実際にとの両方Callable<bool> 型互換性があることに気付きましたSupplier<bool>

私の質問は、意味的にもそうでない場合も、これら2つの間に違いはありますか?もしそうなら、それは何ですか?


動作しないインプレッションコード= SO、動作するがレビューが必要なコード= CodeReview、コードが必要な場合とそうでない場合がある一般的な質問=プログラマーの下にいました。私のコードは実際に機能し、例としてのみあります。私はまた、レビューを求めているのではなく、セマンティクスについて尋ねているだけです。
ダンパントリー14

..何かのセマンティクスについて尋ねることは概念的な質問ではありませんか?
ダンパントリー14

これは概念的な質問であり、このサイトの他の良い質問ほど概念的なものではないと思いますが、実装に関するものではありません。コードは機能しますが、問題はコードに関するものではありません。問題は、「これら2つのインターフェイスの違いは何ですか?」

なぜC#からJavaに切り替えたいのでしょうか。
ディディエA.

2
1つの違いがあります。つまり、Callable.call()は例外をスローし、Supplier.get()はスローしません。これにより、後者はラムダ式でより魅力的になります。
するThorbjörnRavnアンデルセン

回答:


6

簡単な答えは、両方とも機能的インターフェースを使用しているということですが、すべての機能的インターフェースが@FunctionalInterfaceアノテーションを持たなければならないわけではないことに注意する価値があります。JavaDocの重要な部分は次のとおりです。

ただし、コンパイラは、FunctionalInterfaceアノテーションがインターフェイス宣言に存在するかどうかに関係なく、機能インターフェイスの定義を満たすインターフェイスを機能インターフェイスとして扱います。

そして、機能的インターフェースの最も単純な定義は(単に、他の除外なしで)ただ:

概念的には、関数型インターフェースには抽象メソッドが1つだけあります。

したがって、@ 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

さて、両方のインターフェイスCallableSupplier機能的なインターフェイスを作成するのは、抽象メソッドが1つだけ含まれているためです。

  • 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;
    };
}

最初() ->は「a Suppliergives ...」と大まかに解釈でき、2番目は「a gives ...」と解釈できますCallable。ラムダreturn value;の本体でありCallable、それ自体がSupplierラムダの本体です。

ただし、この考案された例の使用法はやや複雑になります。これは、結果をから前に-tingする前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の回答は、受け入れられた回答としてマークした元の回答でした。私はまだこれがもっと便利だと思います。
ダンパントリー

21

2つのインターフェースの基本的な違いの1つは、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

12

お気づきのように、実際にはそれらは同じことを行います(何らかの価値を提供します)が、原則として異なることをすること意図しています:

A Callableは " 結果を返すタスク、 a Supplierは" 結果のサプライヤ "です。つまり、a Callableはまだ実行されていない作業単位Supplierを参照する方法であり、a はまだ不明な値を参照する方法です。

Callableほとんど作業をせず、単純に値を返す可能性があります。Supplier非常に多くの作業を行うこともできます(たとえば、大きなデータ構造を構築する)。しかし、一般的にどちらかであなたが気にすることは彼らの主な目的です。たとえばExecutorServiceCallables は作業単位を実行することを主な目的とするため、s で機能します。遅延ロードされたデータストアは、値を提供されるSupplierことを考慮し、どれだけの作業がかかるかをあまり気にすることなく、を使用します。

区別を表現するもう1つの方法は、aにCallable副作用(たとえば、ファイルへの書き込み)があり、aにSupplierは一般に副作用がないことです。ドキュメントではこれは明示的に言及されていません(要件ではないため)が、これらの用語で考えることをお勧めします。作業がべきSupplierである場合はを使用し、そうでない場合はを使用しCallableます。


2

どちらも特別なセマンティクスのない通常のJavaインターフェイスです。呼び出し可能は、並行APIの一部です。サプライヤーは、新しい関数型プログラミングAPIの一部です。Java8の変更により、ラムダ式から作成できます。@FunctionalInterfaceはコンパイラにインターフェイスが機能していることを確認させ、機能していない場合はエラーを発生させますが、インターフェイスはそのアノテーションが機能的なインターフェイスでラムダによって実装される必要はありません。これは、@ Overrideをマークせずにメソッドをオーバーライドする方法に似ていますが、その逆はできません。

ラムダと互換性のある独自のインターフェイスを定義し、@FunctionalInterfaceアノテーションでドキュメント化できます。ただし、文書化はオプションです。

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

...

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

この特定のインターフェイスはIntPredicateJava で呼び出されることに注意してください。
コンラッド
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.