web-dev-qa-db-ja.com

Java 8ストリームの.min()と.max():なぜこれがコンパイルされるのですか?

注:この質問は前のSO質問であったリンク切れから生じていますが、ここでは...

このコードを参照してください(注:このコードは「機能しない」こと、およびInteger::compareを使用する必要があることはわかっています - リンクした質問から抽出しただけです。

final ArrayList <Integer> list 
    = IntStream.rangeClosed(1, 20).boxed().collect(Collectors.toList());

System.out.println(list.stream().max(Integer::max).get());
System.out.println(list.stream().min(Integer::min).get());

.min().max() のJavadocによると、両方の引数はComparatorになります。それでもここでのメソッド参照は Integer クラスの静的メソッドです。

それで、なぜこれはまったくコンパイルしないのでしょうか。

205
fge

明らかではないので、ここで何が起こっているのか説明させてください。

まず、 Stream.max()Comparator のインスタンスを受け取るので、ストリーム内の項目は次のようになります。あなたはあまり気にする必要はありませんいくつかの最適な順序で、最小値または最大値を見つけるために互いに比較することができます。

それで、質問は、もちろん、なぜ Integer::max が受け入れられるのですか?結局のところ、それはコンパレータではありません!

その答えは、新しいラムダ機能がJava 8で機能するという点にあります。それは、「単一の抽象メソッド」インターフェース、または「SAM」インターフェースとして非公式に知られている概念に依存しています。そのアイデアは、1つの抽象メソッドを持つすべてのインタフェースは、そのメソッドシグネチャがそのインタフェース上の1つのメソッドに一致するすべてのラムダ(またはメソッド参照)によって自動的に実装できるということです。そこで、 Comparator インターフェース(単純版)を調べます。

public Comparator<T> {
    T compare(T o1, T o2);
}

メソッドがComparator<Integer>を探しているなら、それは本質的にこのシグネチャを探しています:

int xxx(Integer o1, Integer o2);

"xxx"を使用しています - メソッド名は照合の目的では使用されていないため

したがって、Integer.min(int a, int b)Integer.max(int a, int b)はどちらも十分に近いため、オートボクシングによってメソッドコンテキストでこれをComparator<Integer>として表示することができます。

240
David M. Lloyd

Comparator機能的なインターフェースであり、Integer::maxはそのインターフェースに準拠しています(自動ボクシング/ボックス解除が考慮された後)。これは2つのint値を取り、intを返します。これは、Comparator<Integer>がtoと予想しているのと同じです(これも、Integer/intの違いを無視するためのものです)。

しかし、Integer.maxComparator.compare意味に準拠していないことを考えると、正しいことをするとは思わないでしょう。そして実際、それは本当に一般的には機能しません。たとえば、1つ小さな変更を加えます。

for (int i = 1; i <= 20; i++)
    list.add(-i);

... maxの値は-20、minの値は-1です。

代わりに、両方の呼び出しでInteger::compareを使用する必要があります。

System.out.println(list.stream().max(Integer::compare).get());
System.out.println(list.stream().min(Integer::compare).get());
115
Jon Skeet

Integer::minComparator<Integer>インターフェースの実装に解決されるため、これは機能します。

Integer::minのメソッド参照はInteger.min(int a, int b)に解決され、IntBinaryOperatorに解決され、おそらくオートボクシングがどこかで発生してBinaryOperator<Integer>になります。

そしてStream<Integer>min()またはmax()メソッドは、Comparator<Integer>インターフェースの実装を要求します。
これで、これは単一のメソッドInteger compareTo(Integer o1, Integer o2)に解決されます。これはBinaryOperator<Integer>型です。

そして、両方の方法がBinaryOperator<Integer>であるため、魔法が起こりました。

19
skiwi

David M. Lloydによる情報とは別に、これを可能にするメカニズムはターゲットタイピングと呼ばれることがあります。

その考え方は、コンパイラーがラムダ式またはメソッド参照に割り当てる型は、式自体だけでなく、それが使用される場所にも依存するということです。

式のターゲットは、結果が割り当てられる変数、または結果が渡されるパラメータです。

ラムダ式とメソッド参照には、その型が見つかると、そのターゲットの型と一致する型が割り当てられます。

詳細については、Javaチュートリアルの 型推論セクション を参照してください。

2
Lii

配列が最大値と最小値を取得するときにエラーが発生したので、次のようにしました。

int max = Arrays.stream(arrayWithInts).max().getAsInt();
int min = Arrays.stream(arrayWithInts).min().getAsInt();
0
JPRLCol