Koleksi, Koleksi, Kumpulkan, Kolektor, Collectos
Koleksi adalah antarmuka leluhur koleksi Java.
Koleksi adalah kelas alat di bawah paket java.util, yang berkonotasi dengan berbagai metode statis untuk memproses koleksi.
java.util.stream.stream#collect (java.util.stream.collector <? super t, a, r>) adalah fungsi aliran yang bertanggung jawab untuk mengumpulkan aliran.
java.util.stream.collector adalah antarmuka untuk mengumpulkan fungsi yang menyatakan fungsi seorang kolektor.
java.util.comparators adalah kelas alat kolektor dengan serangkaian implementasi kolektor bawaan.
Fungsi kolektor
Anda dapat menganggap stream Java8 sebagai iterator dataset mewah dan malas. Mereka mendukung dua jenis operasi: Operasi menengah (misalnya filter, MAP) dan operasi terminal (seperti Count, FindFirst, Foreach, Cured). Operasi menengah dapat dihubungkan untuk mengubah satu aliran menjadi lain. Operasi ini tidak mengkonsumsi aliran, dan tujuannya adalah untuk membuat pipa. Sebaliknya, operasi terminal mengkonsumsi kelas, menghasilkan hasil akhir. Kumpulkan adalah operasi pengurangan, seperti berkurang, dapat menerima berbagai metode sebagai parameter, dan mengumpulkan elemen dalam aliran ke dalam hasil ringkasan. Pendekatan spesifik didefinisikan dengan mendefinisikan antarmuka kolektor baru.
Kolektor yang telah ditentukan sebelumnya
Berikut ini adalah demonstrasi singkat dari kolektor bawaan dasar. Sumber data yang disimulasikan adalah sebagai berikut:
Daftar ArrayList akhir <lid> Dishes = lists.newarraylist (hidangan baru ("babi", false, 800, type.meat), hidangan baru ("daging sapi", false, 700, type.meat), hidangan baru ("ayam", false, 400, type.reveat), hidangan baru ("fries fries", true, 530, type.ote.ody), type), piring baru ("fries fries", true, 530, type.ote.other), lidah), piring baru), 350, "fries fries", true, 530, type), lainnya), typy buah ", true, 120, type.other), hidangan baru (" pizza ", true, 550, type.other), hidangan baru (" udang ", false, 300, type.fish), hidangan baru (" salmon ", false, 450, type.fish));Nilai maksimum, nilai minimum, nilai rata -rata
// Mengapa mengembalikan opsional? Apa yang harus dilakukan jika alirannya nol? Optinal sangat masuk akal pada saat ini opsional <dlid> mostcaloriedish = piring Dishes.stream (). Collect (collectors.avagingint (Dish :: getCalories)); intsummarystatistics ringkasanStatistik = piring. long count = rumarystatistics.getCount (); int max = ringkasanStatistics.getmax (); int min = ringkasanStatistics.getMin (); sum long = rumarystatistics.getSum ();
Indikator statistik sederhana ini memiliki kolektor fungsi kolektor bawaan, terutama untuk fungsi unboxing tipe numerik, yang akan jauh lebih murah daripada secara langsung mengoperasikan jenis pengemasan.
Hubungkan kolektor
Ingin menyatukan unsur -unsur aliran?
// Langsung menghubungkan string join1 = piring.stream (). Map (Dish :: getName) .collect (collectors.joinining ()); // comma string join2 = piring.stream (). Peta (piring :: getName) .collect (collectors.joining (","));tolist
Daftar <string> names = Dishes.stream (). Map (Dish :: GetName) .collect (tolist ());
Peta aliran asli ke dalam aliran elemen tunggal dan kumpulkan sebagai daftar.
Toset
Atur <peed> type = piring.stream (). Peta (piring :: getType) .collect (collector.toSet ());
Kumpulkan jenis sebagai satu set, dan Anda dapat mengulanginya.
tomap
Peta <type, dish> bytype = piring
Kadang -kadang mungkin perlu untuk mengubah array menjadi peta untuk cache, yang memfasilitasi beberapa perhitungan dan akuisisi. TOMAP menyediakan fungsi generasi metode k dan v. (Perhatikan bahwa demo di atas adalah lubang, Anda tidak dapat menggunakannya seperti ini !!! Harap gunakan tomap (fungsi, fungsi, bineroperator))
Di atas hampir adalah kolektor yang paling umum digunakan, dan pada dasarnya cukup. Tetapi sebagai pemula, pemahaman membutuhkan waktu. Untuk benar -benar memahami mengapa ini dapat digunakan untuk mengumpulkan, Anda harus memeriksa implementasi internal. Anda dapat melihat bahwa kolektor ini didasarkan pada java.util.stream.collectors.collectorImpl, yang merupakan kelas implementasi kolektor yang disebutkan di awal. Kolektor khusus akan mempelajari penggunaan spesifik nanti.
Pengurangan Kustom
Beberapa sebelumnya adalah kasus khusus dari proses reduksi yang ditentukan oleh metode pengurangan pabrik. Faktanya, kolektor. Pengurangan dapat digunakan untuk membuat kolektor. Misalnya, cari SUM
Integer TotalCalories = Dishes.stream (). Kumpulkan (reduksi (0, Dish :: getCalories, (i, j) -> i + j)); // Gunakan fungsi bawaan alih -alih fungsi panah integer totalCalories2 = clibes.stream (). Kumpulkan (reduksi (0, Dish :: GetCalories, Integer :: Sum).
Tentu saja, Anda juga dapat menggunakan pengurangan secara langsung
Opsional <Integer> TotalCalories3 = Dishes.stream (). Map (Dish :: GetCalories) .reduce (integer :: sum);
Meskipun tidak apa -apa, jika Anda mempertimbangkan efisiensi, Anda masih harus memilih yang berikut ini
int sum = piring.stream (). maptoint (piring :: getCalories) .sum ();
Pilih solusi terbaik sesuai situasinya
Seperti disebutkan di atas, pemrograman fungsional biasanya menyediakan banyak cara untuk melakukan operasi yang sama. Menggunakan pengumpul kolektor lebih kompleks daripada menggunakan API Stream. Keuntungannya adalah pengumpulan dapat memberikan tingkat abstraksi dan generalisasi yang lebih tinggi, dan lebih mudah digunakan kembali dan menyesuaikan diri.
Saran kami adalah untuk mengeksplorasi berbagai solusi untuk masalah yang ada sebanyak mungkin, selalu pilih yang paling profesional, yang umumnya merupakan keputusan terbaik dalam hal keterbacaan dan kinerja.
Selain menerima nilai awal, pengurangan juga dapat menggunakan item pertama sebagai nilai awal
Opsional <dlid> Mostcaloriedish = Dishes.stream () .collect (reduksi ((d1, d2) -> d1.getCalories ()> d2.getCalories ()? D1: d2));
Mengurangi
Penggunaan pengurangan cukup rumit, dan tujuannya adalah untuk menggabungkan dua nilai menjadi satu nilai.
public static <t, u> collector <t, ?, u> reduksi (u identitas, fungsi <? super t,? Extends u> mapper, bineroperator <u> op)
Pertama, saya melihat 3 obat generik.
U adalah jenis nilai pengembalian. Misalnya, panas yang dihitung dalam demo di atas, U adalah bilangan bulat.
Mengenai T, T adalah jenis elemen dalam aliran. Dari fungsi fungsi, kita dapat mengetahui bahwa fungsi mapper adalah untuk menerima parameter t dan kemudian mengembalikan hasil U. sesuai dengan piringan dalam demo.
? Di tengah daftar generik dengan pengumpul nilai pengembalian, ini mewakili tipe kontainer. Seorang kolektor tentu saja membutuhkan wadah untuk menyimpan data. Di Sini? Ini berarti bahwa tipe wadah tidak pasti. Bahkan, wadah di sini adalah u [].
Tentang parameter:
Identitas adalah nilai awal dari tipe nilai pengembalian, yang dapat dipahami sebagai titik awal akumulator.
Mapper adalah fungsi peta, dan signifikansinya terletak pada mengubah aliran aliran ke aliran jenis yang Anda inginkan.
OP adalah fungsi inti, dan fungsinya adalah bagaimana menangani dua variabel. Di antara mereka, variabel pertama adalah nilai kumulatif, yang dapat dipahami sebagai jumlah, dan variabel kedua adalah elemen berikutnya yang akan dihitung. Dengan demikian, akumulasi tercapai.
Ada juga metode yang kelebihan beban untuk menghilangkan parameter pertama, yang berarti bahwa parameter pertama dalam aliran digunakan sebagai nilai awal.
Public Static <T> Kolektor <T ,?, Opsional <T>> Reduksi (Bineroperator <T> OP)
Mari kita lihat perbedaan antara nilai pengembalian. T mewakili nilai input dan tipe nilai pengembalian, yaitu, jenis nilai input dan tipe nilai outputnya sama. Perbedaan lainnya adalah opsional. Ini karena tidak ada nilai awal, dan parameter pertama mungkin nol. Ketika elemen aliran nol, sangat berarti untuk mengembalikan opsional.
Melihat daftar parameter, hanya Bineryoperator yang tersisa. Bineryoperator adalah antarmuka fungsi tiga, tujuannya adalah untuk menghitung dua parameter dari jenis yang sama dan nilai pengembalian dari jenis yang sama. Itu bisa dipahami sebagai 1> 2? 1: 2, yaitu, temukan nilai maksimum dua angka. Menemukan nilai maksimum adalah pernyataan yang relatif mudah dipahami. Anda dapat menyesuaikan ekspresi Lambda untuk memilih nilai pengembalian. Kemudian, di sini, itu adalah untuk menerima tipe elemen T dari dua aliran dan mengembalikan nilai pengembalian Type T. Tidak apa -apa untuk menggunakan jumlah untuk dipahami.
Dalam demo di atas, ditemukan bahwa fungsi pengurangan dan pengumpulan hampir sama, keduanya mengembalikan hasil akhir. Misalnya, kita dapat menggunakan efek pengurangan tolist:
// Implementasikan secara manual TolistCollector --- Penyalahgunaan Peraturan Pengurangan dan Immutable --- Tidak Dapat Daftar Paralel <Integer> kalori = Dishes.stream (). Map (Dish :: GetCalories) .reduce (Listrist baru <Integer> (), (Daftar <Integer> l, integer E)-{l.add, (E) (E), L. (E), L. (E), L. (E), L. Daftar <Integer> l2) -> {l1.addall (l2);Izinkan saya menjelaskan praktik di atas.
<u> u mengurangi (u identitas, bifunction <u ,? super t, u> akumulator, bineroperator <u> combiner);
U adalah tipe nilai pengembalian, berikut adalah daftar
Bifungsi <u ,? Super T, u> akumulator adalah akumulator, dan tujuannya adalah untuk mengakumulasi nilai dan aturan perhitungan untuk elemen individu. Berikut adalah pengoperasian daftar dan elemen, dan akhirnya daftar pengembalian. Artinya, tambahkan elemen ke daftar.
Bineryoperator <u> Combiner adalah kombiner, dan tujuannya adalah untuk menggabungkan dua variabel jenis nilai pengembalian menjadi satu. Berikut adalah penggabungan dua daftar.
Ada dua masalah dengan solusi ini: satu adalah masalah semantik dan yang lainnya adalah masalah praktis. Masalah semantik adalah bahwa metode pengurangan bertujuan untuk menggabungkan dua nilai untuk menghasilkan nilai baru, yang merupakan pengurangan yang tidak dapat diubah. Sebaliknya, desain metode pengumpulan adalah untuk mengubah wadah dan mengakumulasi hasilnya menjadi output. Ini berarti bahwa cuplikan kode di atas menyalahgunakan metode pengurangan karena mengubah daftar sebagai akumulator di tempatnya. Semantik yang salah untuk menggunakan metode pengurangan juga menciptakan masalah praktis: reduksi ini tidak dapat bekerja secara paralel, karena modifikasi bersamaan dari struktur data yang sama dengan beberapa utas dapat menghancurkan daftar itu sendiri. Dalam hal ini, jika Anda ingin keselamatan utas, Anda perlu mengalokasikan daftar baru sekaligus, dan alokasi objek pada gilirannya akan mempengaruhi kinerja. Inilah sebabnya mengapa Collect cocok untuk mengekspresikan pengurangan pada wadah yang dapat berubah, dan yang lebih penting, itu cocok untuk operasi paralel.
Ringkasan: Pengurangan cocok untuk pengurangan wadah yang tidak dapat diubah, Collect cocok untuk pengurangan wadah yang dapat berubah. Collect cocok untuk paralelisme.
Pengelompokan
Database sering menemukan kebutuhan untuk penjumlahan kelompok, dan menyediakan grup secara primitif. Di Java, jika Anda mengikuti gaya instruksional (menulis loop secara manual), itu akan sangat rumit dan rentan terhadap kesalahan. Java 8 memberikan solusi fungsional.
Misalnya, grup piring berdasarkan jenis. Mirip dengan TOMAP sebelumnya, tetapi nilai pengelompokan bukanlah hidangan, tetapi daftar.
Peta <type, list <lid>> DishesByType = Dishes.stream (). Collect (GroupingBy (Dish :: GetType));
Di Sini
public static <t, k> collector <t, ?, peta <k, daftar <t>>> groupingby (function <? super t,? Extends k> classifier)
Parameter classifier adalah fungsi, yang dirancang untuk menerima satu parameter dan mengonversinya ke jenis lain. Demo di atas adalah untuk mengubah hidangan elemen aliran menjadi tipe tipe, dan kemudian mengelompokkan aliran sesuai dengan jenisnya. Pengelompokan internalnya diimplementasikan melalui HashMap. groupingby (classifier, hashmap :: baru, hilir);
Selain pengelompokan sesuai dengan fungsi properti dari elemen aliran itu sendiri, Anda juga dapat menyesuaikan basis pengelompokan, seperti pengelompokan sesuai dengan rentang panas.
Karena Anda sudah tahu bahwa parameter GroupingBy adalah fungsi dan jenis fungsi parameter adalah DISH, Anda dapat menyesuaikan classifier sebagai:
private caloriClevel getCaloricLevel (piring d) {if (d.getCalories () <= 400) {return caloriClevel.diet; } else if (d.getCalories () <= 700) {return caloriClevel.normal; } else {return caloriClevel.fat; }}Lewati saja parameter
Peta <CaloriClevel, Daftar <dish>> DishesByLevel = Dishes.stream () .collect (GroupingBy (this :: getCaloricLevel));
Pengelompokan multi-level
GroupingBy juga membebani beberapa metode lain, seperti
public static <t, k, a, d> collector <t, ?, peta <k, d >> groupingby (function <? super t,? Extends k> classifier, collector <? super t, a, d> hilir)
Ada banyak obat generik dan kengerian. Mari kita dapatkan pemahaman singkat. Klasifikasi juga merupakan classifier, yang menerima jenis elemen dari aliran dan mengembalikan dasar yang ingin Anda kelompokkan, yaitu, kardinalitas dari basis pengelompokan. Jadi T mewakili jenis elemen saat ini dari aliran, dan k mewakili jenis elemen dari pengelompokan. Parameter kedua adalah hilir, dan hilir adalah kolektor kolektor. Jenis elemen kolektor ini adalah subclass dari T, wadah tipe wadah adalah A, dan tipe nilai pengembalian reduksi adalah D. Artinya, k grup disediakan melalui classifier, dan nilai grup dikurangi melalui kolektor parameter kedua. Kebetulan bahwa kode sumber demo sebelumnya adalah:
public static <t, k> collector <t, ?, peta <k, daftar <t>>> groupingby (function <? super t ,? memperluas k clictifier) {return groupingby (classifier, tolist ()); }Gunakan TOLIST sebagai kolektor pengurangan, dan hasil akhirnya adalah daftar <dlid>, sehingga jenis nilai dari ujung grup adalah daftar <dlid>. Kemudian, tipe nilai dapat ditentukan secara analog oleh kolektor pengurangan, dan ada puluhan juta pengurangan kolektor. Misalnya, saya ingin mengelompokkan nilai lagi, dan pengelompokan juga merupakan semacam pengurangan.
//Multi-level grouping Map<Type, Map<CaloricLevel, List<Dish>>> byTypeAndCalory = dishes.stream().collect( groupingBy(Dish::getType, groupingBy(this::getCaloricLevel)));byTypeAndCalory.forEach((type, byCalory) -> { System.out.println("------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- System.out.println ("/t" + level);Hasil verifikasi adalah:
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- [Dish (name = daging sapi, vegetarian = false, kalori = 700, type = daging)] ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- type = lainnya)]
Ringkasan: Parameter inti dari GroupingBy adalah genator K dan generator V. Generator V dapat berupa semua jenis kolektor kolektor.
Misalnya, generator V dapat menghitung angka, sehingga menerapkan jumlah pilih (*) dalam pernyataan SQL dari tabel grup berdasarkan jenis
Peta <type, long> typescount = piring
SQL Search Group Skor Tertinggi select MAX(id) from table A group by Type
Peta <type, opsional <dlid>> MostCaloricByType = Dishes.stream () .Collect (GroupingBy (Dish :: GetType, Maxby (comparator.cparingint (Dish :: getCalories))))));
Opsional di sini tidak masuk akal, karena tentu saja bukan nol. Kemudian saya harus mengeluarkannya. Menggunakan pengumpulan danthen
Peta <type, Dish> MostCaloricByType = Dishes.stream () .collect (GroupingBy (Dish :: GetType, CollectingandThen (Maxby (comparator.cparingint (Dish :: getCalories)), opsional :: get)));
Tampaknya hasilnya muncul di sini, tetapi ide tidak setuju. Ini mengkompilasi alarm kuning dan mengubahnya menjadi:
Peta <type, Dish> MostCaloricByType = Dishes.stream () .collect (tomap (piring :: getType, function.identity (), Bineryoperator.maxby (membandingkanin (cawan :: getCalories))));
Ya, GroupingBy menjadi tomap, kuncinya masih jenis, nilai masih piring, tetapi ada satu parameter lagi! Lai Di sini kami menanggapi lubang di awal. Demonstrasi TOMAP di awal adalah untuk pemahaman yang mudah. Jika benar -benar digunakan, itu akan dibunuh. Kita tahu bahwa mengatur ulang daftar ke dalam peta pasti akan menghadapi masalah yang sama. Kapan k sama, apakah V mengesampingkan atau mengabaikannya? Metode demo sebelumnya adalah memasukkan k lagi dan melemparkan pengecualian secara langsung ketika k hadir:
java.lang.illegalstateException: duplikat piringan kunci (name = babi, sayuran = false, kalori = 800, type = daging) di java.util.stream.collectors.lambda $ throwingmerger $ 0 (collector.java:133)
Cara yang benar adalah menyediakan fungsi untuk menangani konflik. Dalam demo ini, prinsip penanganan konflik adalah menemukan yang terbesar, yang hanya memenuhi persyaratan kami untuk mengelompokkan dan menemukan yang terbesar. (Saya benar -benar tidak ingin melakukan pembelajaran fungsional Java 8 lagi, saya merasa ada jebakan masalah kinerja di mana -mana)
Lanjutkan Pemetaan SQL Database, select sum(score) from table a group by Type
Peta <type, integer> TotalCaloriesByType = Dishes.stream () .collect (GroupingBy (Dish :: GetType, SummingInt (Dish :: GetCalories))));
Namun, kolektor lain yang sering digunakan bersama dengan GroupingBy dihasilkan oleh metode pemetaan. Metode ini menerima dua parameter: satu fungsi mengubah elemen dalam aliran, dan yang lainnya mengumpulkan objek hasil yang diubah. Tujuannya adalah untuk menerapkan fungsi pemetaan untuk setiap elemen input sebelum akumulasi, sehingga kolektor yang menerima elemen dari jenis tertentu dapat beradaptasi dengan berbagai jenis objek. Biarkan saya melihat contoh praktis menggunakan kolektor ini. Misalnya, Anda ingin mendapatkan level kalori apa yang ada di menu untuk setiap jenis hidangan. Kami dapat menggabungkan pengumpul pengelompokan dan pemetaan sebagai berikut:
Peta <type, atur <caloriClevel>> caloriclevelsbytype = dishes.stream () .collect (groupingby (piring :: getType, pemetaan (ini :: getCaloriclevel, toset ())));
ToSet di sini menggunakan hashset secara default, dan Anda juga dapat secara manual menentukan toclection implementasi spesifik (hashset :: baru)
Partisi
Partisi adalah kasus khusus pengelompokan: predikat (fungsi yang mengembalikan nilai boolean) digunakan sebagai fungsi klasifikasi, yang disebut fungsi partisi. Fungsi partisi mengembalikan nilai boolean, yang berarti jenis kunci dari peta yang dikelompokkan adalah boolean, sehingga dapat dibagi menjadi hingga dua kelompok: benar atau salah. Misalnya, jika Anda seorang vegetarian, Anda mungkin ingin memisahkan menu dengan vegetarian dan non-vegetarian:
Peta <boolean, daftar <lid>> partisionedmenu = clibes.stream (). Kumpulkan (partisi (piring :: isvetarian));
Tentu saja, menggunakan filter dapat mencapai efek yang sama:
Daftar <dlid> vegetariandishes = piring
Keuntungan dari partisi adalah menyimpan dua salinan, yang berguna ketika Anda ingin mengklasifikasikan daftar. Pada saat yang sama, seperti GroupingBy, PartitioningBy memiliki metode yang kelebihan beban, yang dapat menentukan jenis nilai pengelompokan.
Peta <boolean, peta <type, list <liss>> vegetariandishesbytype = dishes.stream () .collect (Partitionby (Dish :: isVetarian, Groupingby (Dish :: Gettype))); peta <boolean, integer> vegetari dan pemasangan vegetari dan pemasangan: clishes. Dishect. Summingint (Dish :: getCalories)))); peta <boolean, piring> mostcaloricpartitionedbyvetarian = dishes.stream () .collect (partisi (piring :: isvetarian, collecting dan (maxby (perbandingan (cawan :: getCalories)), opsional :: get)));
Sebagai contoh terakhir dari menggunakan PartitioningBy Collector, kami mengesampingkan model data menu untuk melihat contoh yang lebih kompleks dan menarik: membagi array menjadi angka utama dan non-prime.
Pertama, tentukan fungsi partisi utama:
private boolean isPrime (kandidat int) {int candidateroot = (int) math.sqrt ((ganda) kandidat); return intstream.rangeclosed (2, candidateroot) .noneMatch (i -> kandidat % i == 0);}Kemudian temukan angka utama dan non-prime dari 1 hingga 100
Peta <boolean, daftar <integer>> partitionprimes = intStream.rangeclosed (2, 100) .boxed () .collect (partitioningby (this :: isPrime));