私はこのチュートリアルを読んでいた Java 8
interface Formula {
double calculate(int a);
default double sqrt(int a) {
return Math.sqrt(a);
}
}
そして言った
ラムダ式内からデフォルトのメソッドにアクセスすることはできません。次のコードはコンパイルされません。
Formula formula = (a) -> sqrt( a * 100);
しかし、彼はそれが不可能な理由を説明しませんでした。コードを実行しましたが、エラーが発生しました。
互換性のない型:数式は機能的なインターフェイスではありません `
では、なぜそれが不可能なのか、エラーの意味は何ですか?インターフェイスは、1つの抽象メソッドを持つ機能インターフェイスの要件を満たします。
それは多かれ少なかれ範囲の問題です。 JLSから
匿名クラス宣言に表示されるコードとは異なり、ラムダ本体に表示される名前と
this
およびsuper
キーワードの意味は、参照される宣言のアクセシビリティとともに、周囲のコンテキストと同じです。 (ラムダパラメータが新しい名前を導入することを除いて)。
あなたの試みた例では
_Formula formula = (a) -> sqrt( a * 100);
_
スコープには、sqrt
という名前の宣言が含まれていません。
これはJLSでも示唆されています
実際には、ラムダ式がそれ自体について話す必要があることは珍しいです(再帰的に呼び出すか、他のメソッドを呼び出すため)名前を使用して、それ以外の場合はシャドウイングされるクラス(
this
、toString()
)を参照することがより一般的です。 ラムダ式がそれ自体を参照する必要がある場合(this
を介して)、代わりにメソッド参照または匿名の内部クラスを使用する必要があります。
実装できたと思う。彼らはそれを許可しないことを選んだ。
ラムダ式は、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);
}
};
それは正確に真実ではありません。ラムダ式ではデフォルトのメソッドを使用できます。
_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;
_
まったく同じラムダ式を複数の型に「キャスト」できます。ラムダが型を持たないかのように思います。これは、適切なパラメーターを持つ任意のインターフェイスに使用できる特別な型なし関数です。しかし、その制限は、将来の型のメソッドを知ることができないため使用できないことを意味します。
これは議論に少しだけ追加しますが、とにかく面白いと思いました。
問題を確認する別の方法は、自己参照ラムダの観点から考えることです。
例えば:
Formula formula = (a) -> formula.sqrt(a * 100);
ラムダが実行されるまでにformula
参照がすでに初期化されている必要があるため(つまり、formula
が完了するまでformula.apply()
を実行する方法はないため)適切に初期化されており、その場合、ラムダの本体、apply
の本体から、同じ変数を参照することが可能でなければなりません)。
ただし、これも機能しません。興味深いことに、それは最初は可能でした。 Maurice Naftalinが Lambda FAQ Webサイト に文書化していたことがわかります。しかし、何らかの理由でこの機能のサポートは 最終的に削除されました =。
この質問に対する他の回答で与えられた提案のいくつかは、ラムダメーリングリストのまさにその議論ですでに言及されています。