Java 8 stream .min () dan .max (): mengapa ini dikompilasi?

215

Catatan: pertanyaan ini berasal dari tautan mati yang merupakan pertanyaan SO sebelumnya, tetapi begini ...

Lihat kode ini ( catatan: Saya tahu bahwa kode ini tidak akan "berfungsi" dan yang Integer::compareharus digunakan - Saya baru saja mengekstraknya dari pertanyaan yang ditautkan ):

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());

Menurut javadoc dari .min()dan .max(), argumen keduanya harus a Comparator. Namun di sini referensi metode adalah metode statis Integerkelas.

Jadi, mengapa ini dikompilasi?

Fge
sumber
6
Perhatikan bahwa itu tidak berfungsi dengan baik, itu seharusnya menggunakan Integer::comparebukan Integer::maxdan Integer::min.
Christoffer Hammarström
@ ChristofferHammarström Saya tahu itu; perhatikan bagaimana saya katakan sebelum ekstrak kode "Saya tahu, itu tidak masuk akal"
fge
3
Saya tidak berusaha mengoreksi Anda, saya memberi tahu orang-orang secara umum. Anda membuatnya terdengar seolah-olah Anda berpikir bahwa bagian yang absurd adalah bahwa metode Integerbukan metode Comparator.
Christoffer Hammarström

Jawaban:

242

Izinkan saya menjelaskan apa yang terjadi di sini, karena tidak jelas!

Pertama, Stream.max()terima contoh Comparatoragar item dalam aliran dapat dibandingkan satu sama lain untuk menemukan minimum atau maksimum, dalam urutan optimal yang tidak perlu Anda khawatirkan terlalu banyak.

Jadi pertanyaannya, tentu saja, mengapa Integer::maxditerima? Bagaimanapun, ini bukan pembanding!

Jawabannya adalah bagaimana fungsi lambda baru berfungsi di Java 8. Ini bergantung pada konsep yang secara informal dikenal sebagai antarmuka "metode abstrak tunggal", atau antarmuka "SAM". Idenya adalah bahwa setiap antarmuka dengan satu metode abstrak dapat secara otomatis diimplementasikan oleh lambda - atau referensi metode - yang tanda tangannya metode cocok untuk satu metode pada antarmuka. Jadi memeriksa Comparatorantarmuka (versi sederhana):

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

Jika suatu metode mencari Comparator<Integer>, maka pada dasarnya mencari tanda tangan ini:

int xxx(Integer o1, Integer o2);

Saya menggunakan "xxx" karena nama metode tidak digunakan untuk tujuan pencocokan .

Oleh karena itu, keduanya Integer.min(int a, int b)dan Integer.max(int a, int b)cukup dekat sehingga autoboxing akan memungkinkan ini muncul sebagai Comparator<Integer>dalam konteks metode.

David M. Lloyd
sumber
28
atau alternatif: list.stream().mapToInt(i -> i).max().get().
assylias
13
@assylias Anda ingin menggunakan .getAsInt()alih-alih get(), karena Anda berurusan dengan OptionalInt.
skiwi
... saat yang kami coba adalah menyediakan pembanding khusus untuk suatu max()fungsi!
Manu343726
Perlu dicatat bahwa "Antarmuka SAM" ini sebenarnya disebut "Antarmuka Fungsional" dan yang melihat Comparatordokumentasi kita dapat melihat bahwa itu dihiasi dengan anotasi @FunctionalInterface. Dekorator ini adalah keajaiban yang memungkinkan Integer::maxdan Integer::mindiubah menjadi Comparator.
Chris Kerekes
2
@ChrisKerekes dekorator @FunctionalInterfaceterutama untuk tujuan dokumentasi saja, karena kompiler dapat dengan senang hati melakukan ini dengan antarmuka apa pun dengan satu metode abstrak tunggal.
errantlinguist
117

Comparatoradalah antarmuka fungsional , dan Integer::maxsesuai dengan antarmuka itu (setelah autoboxing / unboxing dipertimbangkan). Dibutuhkan dua intnilai dan mengembalikan int- seperti yang Anda harapkan Comparator<Integer>untuk (sekali lagi, menyipitkan mata untuk mengabaikan perbedaan Integer / int).

Namun, saya tidak akan mengharapkan untuk melakukan hal yang benar, mengingat bahwa Integer.maxtidak sesuai dengan semantik dari Comparator.compare. Dan memang itu tidak benar-benar bekerja secara umum. Misalnya, buat satu perubahan kecil:

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

... dan sekarang maxnilainya -20 dan minnilainya -1.

Sebaliknya, kedua panggilan harus menggunakan Integer::compare:

System.out.println(list.stream().max(Integer::compare).get());
System.out.println(list.stream().min(Integer::compare).get());
Jon Skeet
sumber
1
Saya tahu tentang antarmuka fungsional; dan saya tahu mengapa itu memberikan hasil yang salah. Saya hanya ingin tahu bagaimana mungkin kompiler tidak meneriaki saya
fge
6
@Fge: Yah, apakah itu unboxing yang tidak Anda ketahui? (Saya belum melihat bagian yang tepat.) A Comparator<Integer>pasti int compare(Integer, Integer)... itu tidak membingungkan bahwa Jawa memungkinkan referensi metode int max(int, int)untuk mengkonversi ke ...
Jon Skeet
7
@fge: Apakah kompiler seharusnya mengetahui semantik dari Integer::max? Dari sudut pandangnya Anda melewati fungsi yang memenuhi spesifikasinya, itu saja yang benar-benar bisa berjalan.
Mark Peters
6
@fge: Khususnya, jika Anda memahami bagian dari apa yang terjadi, tetapi tergelitik tentang satu aspek spesifik dari itu, ada baiknya menjelaskannya dalam pertanyaan untuk menghindari orang-orang membuang-buang waktu untuk menjelaskan bagian-bagian yang sudah Anda ketahui.
Jon Skeet
20
Saya pikir masalah yang mendasarinya adalah jenis tanda tangan Comparator.compare. Itu harus mengembalikan enumdari {LessThan, GreaterThan, Equal}, bukan int. Dengan begitu, antarmuka fungsional tidak akan benar-benar cocok dan Anda akan mendapatkan kesalahan kompilasi. TKI: tipe tanda tangan dari Comparator.comparetidak cukup menangkap semantik dari apa artinya membandingkan dua objek, dan dengan demikian antarmuka lain yang sama sekali tidak ada hubungannya dengan membandingkan objek secara tidak sengaja memiliki tipe tanda tangan yang sama.
Jörg W Mittag
19

Ini berfungsi karena Integer::minmemutuskan untuk mengimplementasikan Comparator<Integer>antarmuka.

Referensi metode Integer::minresolves to Integer.min(int a, int b), resolved to IntBinaryOperator, dan mungkin autoboxing terjadi di suatu tempat membuatnya menjadi BinaryOperator<Integer>.

Dan metode min()resp max()dari antarmuka Stream<Integer>meminta Comparator<Integer>untuk diimplementasikan.
Sekarang ini teratasi dengan metode tunggal Integer compareTo(Integer o1, Integer o2). Yang merupakan tipe BinaryOperator<Integer>.

Dan dengan demikian keajaiban telah terjadi karena kedua metode adalah a BinaryOperator<Integer>.

skiwi
sumber
Tidak sepenuhnya benar untuk mengatakan Integer::minalat itu Comparable. Ini bukan tipe yang bisa mengimplementasikan apa pun. Tetapi dievaluasi menjadi objek yang mengimplementasikan Comparable.
Lii
1
@ Lii Terima kasih, saya memperbaikinya sekarang.
skiwi
Comparator<Integer>adalah antarmuka metode tunggal abstrak (alias "fungsional"), dan Integer::minmemenuhi kontraknya, sehingga lambda dapat diartikan sebagai ini. Saya tidak tahu bagaimana Anda melihat BinaryOperator berperan di sini (atau IntBinaryOperator, baik) - tidak ada hubungan subtyping antara itu dan Pembanding.
Paŭlo Ebermann
2

Terlepas dari informasi yang diberikan oleh David M. Lloyd orang dapat menambahkan bahwa mekanisme yang memungkinkan ini disebut pengetikan target .

Idenya adalah bahwa jenis kompiler menetapkan untuk ekspresi lambda atau referensi metode tidak hanya bergantung pada ekspresi itu sendiri, tetapi juga pada di mana ia digunakan.

Target ekspresi adalah variabel yang hasilnya ditetapkan atau parameter yang hasilnya diteruskan.

Ekspresi Lambda dan referensi metode diberikan jenis yang cocok dengan jenis target mereka, jika jenis tersebut dapat ditemukan.

Lihat bagian Ketikkan Inferensi di Tutorial Java untuk informasi lebih lanjut.

Lii
sumber
1

Saya mengalami kesalahan dengan array mendapatkan maks dan min jadi solusi saya adalah:

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