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


215

注:この質問は、以前の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());

ののjavadocによる.min().max()、両方の引数がなければなりませんComparator。ただし、ここではメソッド参照はIntegerクラスの静的メソッドを参照しています。

では、なぜこれがまったくコンパイルされるのでしょうか?


6
正しく機能しないことに注意してください。Integer::compare代わりにInteger::maxおよびを使用する必要がありInteger::minます。
ChristofferHammarström2015年

@ChristofferHammarström私はそれを知っています。私は、コードの抽出の前に言ったか「私が知っている、それは馬鹿げている」に注意
FGE

3
私はあなたを正すつもりはありませんでした、私は一般の人々に言っています。ばかげている部分はの方法でIntegerはないと思っているかのように聞こえましたComparator
ChristofferHammarström2015年

回答:


242

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

最初に、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>方法の文脈です。


28
または代わりに:list.stream().mapToInt(i -> i).max().get()
アッシリアス2014年

13
@assylias を扱っているので、.getAsInt()代わりに使用したい。get()OptionalInt
skiwi 2014年

...私たちが試みているのは、max()関数にカスタムコンパレータを提供することだけである場合です。
Manu343726 2014年

この「SAMインターフェース」は実際には「機能インターフェース」と呼ばれ、Comparatorドキュメントを見ると、アノテーションで装飾されていることがわかり@FunctionalInterfaceます。このデコレータは可能に魔法であるInteger::maxInteger::minに変換しますComparator
Chris Kerekes

2
@ChrisKerekesデコレーター@FunctionalInterfaceは、コンパイラーが単一の抽象メソッドを持つ任意のインターフェースでこれを喜んで行うことができるため、主に文書化の目的のみです。
誤った言語学者、2017年

117

Comparator機能的なインターフェースでありInteger::max、そのインターフェースに準拠しています(オートボクシング/アンボクシングが考慮された後)。これは2つのint値を取り、int-を期待するのComparator<Integer>と同じように(繰り返しますが、Integer / intの違いを無視するように目を細めて)を返します。

ただし、それがInteger.maxセマンティクスに準拠していないことを考えると、それが正しいことを行うとは思いませんComparator.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());

1
機能的なインターフェースについて知っています。そして、なぜそれが間違った結果をもたらすのか知っています。コンパイラーが
一体どうして

6
@fge:まあ、それはあなたが不明瞭だった開梱のことでしたか?(私は正確にその部分を調べていません。)A Comparator<Integer>int compare(Integer, Integer)... Javaのメソッド参照がそれint max(int, int)に変換することを許可しているのは気が遠くなるようなことではありません...
Jon Skeet

7
@fge:コンパイラはのセマンティクスを知っているはずInteger::maxですか?その観点から、その仕様を満たす関数を渡しました。
Mark Peters、

6
@fge:特に、状況の一部を理解しているが、その特定の側面に興味がある場合は、質問でそれを明確にして、すでに知っているビットを説明するのに時間を浪費しないようにすることをお勧めします。
Jon Skeet、2014年

20
根本的な問題はの型シグネチャだと思いますComparator.compare。それは返す必要がありますenum{LessThan, GreaterThan, Equal}、ありませんint。このようにすると、機能的なインターフェイスが実際には一致せず、コンパイルエラーが発生します。IOW:の型シグネチャはComparator.compare、2つのオブジェクトを比較することの意味のセマンティクスを適切にキャプチャしていません。したがって、オブジェクトの比較とはまったく関係のない他のインターフェイスは、誤って同じ型シグネチャを持っています。
イェルクWミッターク

19

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

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

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

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


がをInteger::min実装していると言うことは完全に正しくありませんComparable。なんでも実装できるタイプではありません。しかし、それはを実装するオブジェクトに評価されますComparable
Lii 14

1
@Liiありがとう、今すぐ修正しました。
skiwi 2014

Comparator<Integer>単一抽象メソッド(別名「機能的」)インターフェースでありInteger::min、その契約を満たしているため、ラムダはこれと解釈できます。BinaryOperator(またはIntBinaryOperator)がどのように機能するかはわかりません。これとコンパレータの間にサブタイプの関係はありません。
–PaŭloEbermann 2017

2

David M. Lloydによって提供された情報とは別に、これを可能にするメカニズムをターゲットタイピングと呼ぶことができます

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

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

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

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


1

最大と最小を取得する配列でエラーが発生したため、私の解決策は次のとおりです。

int max = Arrays.stream(arrayWithInts).max().getAsInt();
int min = Arrays.stream(arrayWithInts).min().getAsInt();
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.