47

ライターがコードを示したJava 8 のこのチュートリアルを読んでいました。

interface Formula {
    double calculate(int a);

    default double sqrt(int a) {
        return Math.sqrt(a);
    }
}

そして言った

ラムダ式内からデフォルトのメソッドにアクセスすることはできません。次のコードはコンパイルされません。

Formula formula = (a) -> sqrt( a * 100);

しかし、彼はそれが不可能な理由を説明しませんでした。コードを実行したところ、エラーが発生しました。

互換性のない型: 数式は機能的なインターフェイスではありません`

では、なぜそれが不可能なのか、またはエラーの意味は何ですか? このインターフェースは、1 つの抽象メソッドを持つ関数インターフェースの要件を満たします。

4

5 に答える 5

30

それは多かれ少なかれ範囲の問題です。JLSから

匿名クラス宣言に現れるコードとは異なり、ラムダ本体に現れる名前とthisandsuperキーワードの意味は、参照される宣言のアクセシビリティと共に、周囲のコンテキストと同じです (ラムダ パラメーターが新しい名前を導入することを除いて)。

あなたの試みた例では

Formula formula = (a) -> sqrt( a * 100);

スコープに name の宣言が含まれていませんsqrt

これはJLSでも示唆されています

実際には、ラムダ式がそれ自体について話す必要がある(再帰的に呼び出すか、他のメソッドを呼び出す) 必要があることはまれですが、名前を使用して囲んでいるクラス内のものを参照することはより一般的です。それ以外の場合は、シャドウ ( thistoString()) になります。ラムダ式がそれ自体を参照する必要がある場合 ( 経由のようにthis)、代わりにメソッド参照または匿名内部クラスを使用する必要があります。

実装できたと思います。彼らはそれを許可しないことにしました。

于 2015-10-13T17:37:59.777 に答える
14

thisラムダ式は、式を囲むスコープ内で同じものを表すという点で、匿名クラスとはまったく異なる方法で機能します。

たとえば、これはコンパイルされます

class Main {

    public static void main(String[] args) {
        new Main().foo();
    }

    void foo() {
        System.out.println(this);
        Runnable r = () -> {
            System.out.println(this);
        };
        r.run();
    }
}

そして、それは次のようなものを出力します

Main@f6f4d33
Main@f6f4d33

つまり、ラムダ式によって作成されたオブジェクトでthisMainなく、 です。

したがって、参照のタイプが、またはサブタイプではなく、メソッドがないsqrtため、ラムダ式で使用することはできません。thisFormulasqrt

Formulaただし、機能的なインターフェイスであり、コード

Formula f = a -> a;

問題なくコンパイルして実行します。

これにはラムダ式を使用できませんが、次のように匿名クラスを使用して実行できます。

Formula f = new Formula() { 
    @Override 
    public double calculate(int a) { 
        return sqrt(a * 100); 
    }
};
于 2015-10-13T17:38:07.397 に答える
9

それは正確には真実ではありません。デフォルトのメソッドはラムダ式で使用できます。

interface Value {
    int get();

    default int getDouble() {
        return get() * 2;
    }
}

public static void main(String[] args) {
    List<Value> list = Arrays.asList(
            () -> 1,
            () -> 2
        );
    int maxDoubled = list.stream()
        .mapToInt(val -> val.getDouble())
        .max()
        .orElse(0);
    System.out.println(maxDoubled);
}

期待どおりに印刷4され、ラムダ式内でデフォルトのメソッドを使用します ( .mapToInt(val -> val.getDouble()))

あなたの記事の著者がここでやろうとしていること

Formula formula = (a) -> sqrt( a * 100);

Formulaラムダ式を介して直接、関数インターフェイスとして機能する を定義することです。

上記のコード例、Value value = () -> 5またはFormulaas interface などで問題なく動作します

Formula formula = (a) -> 2 * a * a + 1;

しかし

Formula formula = (a) -> sqrt( a * 100);

this.( )sqrtメソッドにアクセスしようとしているがアクセスできないため、失敗します。仕様によると、ラムダはその周囲からスコープを継承します。つまりthis、ラムダの内側は、ラムダのすぐ外側と同じものを参照します。そして、sqrt外に方法はありません。

これについての私の個人的な説明: ラムダ式の内部では、ラムダが「変換」される具体的な関数インターフェイスが明確ではありません。比較

interface NotRunnable {
    void notRun();
}

private final Runnable r = () -> {
    System.out.println("Hello");
};

private final NotRunnable r2 = r::run;

まったく同じラムダ式を複数の型に「キャスト」できます。ラムダに型がないかのように考えます。これは、適切なパラメーターを持つ任意のインターフェイスに使用できる特別な型のない関数です。しかし、その制限は、それを知ることができないため、future 型のメソッドを使用できないことを意味します。

于 2015-10-13T17:21:03.390 に答える
2

これは議論にほとんど追加されませんが、とにかく興味深いと思いました。

問題を見る別の方法は、自己参照ラムダの観点から考えることです。

例えば:

Formula formula = (a) -> formula.sqrt(a * 100);

ラムダが実行されるまでに、参照はすでに初期化されている必要があるため、これは理にかなっているように思われます (つまり、適切に初期化されるまで実行するformula方法はありません。の本体である lambda は、同じ変数を参照できるはずです)。formula.apply()formulaapply

ただし、これも機能しません。興味深いことに、最初は可能でした。Maurice Naftalin が彼のLambda FAQ Web サイトに文書化していたことがわかります。しかし、何らかの理由で、この機能のサポートは最終的に削除されました。

この質問に対する他の回答で与えられた提案のいくつかは、ラムダ メーリング リストのまさにその議論で既に言及されています。

于 2015-10-15T03:04:19.187 に答える