web-dev-qa-db-ja.com

ラムダ式でデフォルトのメソッドを使用できないのはなぜですか?

私はこのチュートリアルを読んでいた Java 8

interface Formula {
    double calculate(int a);

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

そして言った

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

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

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

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

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

41
codegasmer

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

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

あなたの試みた例では

_Formula formula = (a) -> sqrt( a * 100);
_

スコープには、sqrtという名前の宣言が含まれていません。

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

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

実装できたと思う。彼らはそれを許可しないことを選んだ。

26

ラムダ式は、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

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

そのため、sqrt参照の型がthisまたはサブタイプではなく、Formulaメソッドがないため、ラムダ式でsqrtを使用できません。

Formulaは機能的なインターフェイスですが、コードは

Formula f = a -> a;

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

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

Formula f = new Formula() { 
    @Override 
    public double calculate(int a) { 
        return sqrt(a * 100); 
    }
};
12
Paul Boddington

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

_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で、またはインターフェースとしてFormulaを使用して、それは正常に機能します。

_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;
_

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

6
zapl

これは議論に少しだけ追加しますが、とにかく面白いと思いました。

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

例えば:

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

ラムダが実行されるまでにformula参照がすでに初期化されている必要があるため(つまり、formulaが完了するまでformula.apply()を実行する方法はないため)適切に初期化されており、その場合、ラムダの本体、applyの本体から、同じ変数を参照することが可能でなければなりません)。

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

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

1
Edwin Dalorzo