filter menentukan elemen mana yang lolos; map menentukan bentuk elemen selanjutnya. Dalam pipeline toko kita, data mentah berupa record Produk utuh, padahal laporan harga hanya butuh field harga, dan label struk butuh nama dalam huruf kapital. Operasi map mengubah setiap elemen menjadi nilai baru — berganti tipe jika perlu — sementara flatMap menangani kasus yang lebih licik: satu elemen yang berisi banyak nilai, seperti pesanan berisi daftar item. Pada Chapter ini kita akan memakai keduanya, ditutup mapToInt untuk perhitungan numerik tanpa pembungkusan Integer.
Mengubah Tiap Elemen dengan map #
Method map menerima Function<? super T, ? extends R> — fungsi dari elemen lama ke elemen baru — dan menghasilkan Stream<R>. Pemetaannya bersifat satu-ke-satu: tiga elemen masuk, tiga elemen keluar, masing-masing hasil olahan dari pasangannya. Karena tipe keluaran ditentukan oleh fungsi, map adalah tempat pipeline berganti tipe: Stream<Produk> menjadi Stream<String>, lalu Stream<String> bisa berlanjut menjadi Stream<Integer>:
import java.util.List;
public class TransformasiMap {
record Produk(String nama, String kategori, int harga) {}
public static void main(String[] args) {
List<Produk> produk = List.of(
new Produk("Kopi Arabika", "minuman", 85000),
new Produk("Teh Hijau", "minuman", 45000),
new Produk("Roti Gandum", "makanan", 30000)
);
// Objek -> field: Produk dipetakan menjadi nama produk
List<String> nama = produk.stream().map(Produk::nama).toList();
System.out.println("Nama : " + nama);
// Transformasi teks: setiap nama menjadi huruf besar
List<String> namaBesar = produk.stream()
.map(p -> p.nama().toUpperCase())
.toList();
System.out.println("Huruf besar: " + namaBesar);
// Transformasi tipe: String menjadi panjang karakternya
List<Integer> panjangNama = nama.stream().map(String::length).toList();
System.out.println("Panjang : " + panjangNama);
}
}Output yang Dihasilkan:
Nama : [Kopi Arabika, Teh Hijau, Roti Gandum]
Huruf besar: [KOPI ARABIKA, TEH HIJAU, ROTI GANDUM]
Panjang : [12, 9, 11]Mari kita bedah tiga pemetaan tersebut:
- Baris 15 (
map(Produk::nama)): method reference ke accessor record — bentuk paling umum darimap, memetakan objek ke salah satu fieldnya.Stream<Produk>berubah menjadiStream<String>berisi tiga nama. - Baris 19-21 (
map(p -> p.nama().toUpperCase())): lambda yang mengolah isi field, bukan sekadar mengambilnya. Nilai tetap bertipeString, tetapi isinya telah diubah menjadi huruf kapital. - Baris 25 (
map(String::length)): pemetaan berganti tipe dariStringkeInteger—"Kopi Arabika"menjadi12,"Teh Hijau"menjadi9,"Roti Gandum"menjadi11. Pipeline tidak peduli tipe berganti; fungsi yang menentukan.
Meratakan Struktur Bersarang dengan flatMap #
Bayangkan kita mencoba memakai map untuk menggabungkan isi keranjang belanja: sumbernya List<List<String>>, dan map(List::stream) memetakan tiap List menjadi Stream — hasilnya Stream<Stream<String>>, bukan satu aliran item. Di sinilah flatMap masuk: fungsi yang diberikan tetap menghasilkan stream (atau array) per elemen, tetapi flatMap meratakan semua sub-stream tersebut menjadi satu aliran datar. Perbedannya dapat diringkas begini: map membungkus hasil, flatMap membukanya.
import java.util.Arrays;
import java.util.List;
public class MeratakanData {
record Pesanan(String pelanggan, List<String> item) {}
public static void main(String[] args) {
// Struktur bersarang List<List<String>> diratakan dengan List::stream
List<List<String>> keranjangPerHari = List.of(
List.of("Kopi", "Roti"),
List.of("Teh", "Madu", "Gula"),
List.of("Air")
);
List<String> semuaItem = keranjangPerHari.stream()
.flatMap(List::stream)
.toList();
System.out.println("Rata satu tingkat : " + semuaItem);
// Record berisi List: item lintas pesanan digabung
List<Pesanan> pesanan = List.of(
new Pesanan("Budi", List.of("Kopi", "Roti")),
new Pesanan("Siti", List.of("Teh")),
new Pesanan("Dewi", List.of("Madu", "Gula"))
);
List<String> itemTerjual = pesanan.stream()
.flatMap(p -> p.item().stream())
.toList();
System.out.println("Item terjual : " + itemTerjual);
// List<int[]> diratakan dengan Arrays::stream -> IntStream
List<int[]> stokPerRak = List.of(new int[]{10, 20}, new int[]{5, 8, 12});
int totalStok = stokPerRak.stream()
.flatMapToInt(Arrays::stream)
.sum();
System.out.println("Total stok : " + totalStok);
// Teks multi-baris -> daftar kata dengan lines() lalu flatMap
String deskripsi = "Kopi Arabika Premium\nBerasal dari Gayo";
List<String> kata = deskripsi.lines()
.flatMap(baris -> Arrays.stream(baris.split(" ")))
.toList();
System.out.println("Kata : " + kata);
}
}Output yang Dihasilkan:
Rata satu tingkat : [Kopi, Roti, Teh, Madu, Gula, Air]
Item terjual : [Kopi, Roti, Teh, Madu, Gula]
Total stok : 55
Kata : [Kopi, Arabika, Premium, Berasal, dari, Gayo]Empat sumber berbeda, satu pola yang sama — mari kita amati satu per satu:
- Baris 15-17 (
flatMap(List::stream)): tiapList<String>dipetakan menjadi stream isinya, lalu keenam item dari tiga keranjang diratakan menjadi satuList.List::streamadalah method reference standar untuk meratakanList<List<T>>. - Baris 26-28 (
flatMap(p -> p.item().stream())): kasus paling sering di aplikasi nyata — record yang memuatList.flatMapmenggabungkan item milik Budi, Siti, dan Dewi menjadi satu daftar transaksi; pemetaannya satu-ke-banyak karena satu pesanan memuat banyak item. - Baris 32-35 (
flatMapToInt(Arrays::stream)): array primitifint[]tidak bisa diratakan denganList::stream;Arrays::streammengubah tiap array menjadiIntStream, dan varianflatMapToIntmeratakannya menjadi satuIntStreamsehinggasum()menghitung 10+20+5+8+12 = 55. - Baris 39-42 (
lines()+split):String.lines()sejak Java 11 memecah teks multi-baris menjadiStream<String>per baris;flatMapkemudian memecah tiap baris menjadi kata-kata. Dua tingkat pemecahan, satu pipeline datar.
mapToInt untuk Data Numerik #
Untuk menghitung angka, ada varian khusus: mapToInt memetakan elemen ke int dan langsung menghasilkan IntStream — stream primitif tanpa pembungkusan Integer. Keuntungannya dua lapis: operasi akhir numerik seperti sum(), average(), min(), max() tersedia langsung, dan JVM tidak bolak-balik membungkus nilai ke objek Integer saat menghitung:
import java.util.List;
public class HitungOmzet {
record Produk(String nama, String kategori, int harga) {}
public static void main(String[] args) {
List<Produk> produk = List.of(
new Produk("Kopi Arabika", "minuman", 85000),
new Produk("Teh Hijau", "minuman", 45000),
new Produk("Roti Gandum", "makanan", 30000),
new Produk("Madu Hutan", "makanan", 120000)
);
// mapToInt: field harga menjadi IntStream, lalu dijumlahkan
int totalMinuman = produk.stream()
.filter(p -> p.kategori().equals("minuman"))
.mapToInt(Produk::harga)
.sum();
System.out.println("Total harga minuman : " + totalMinuman);
// Rata-rata harga seluruh produk
double rataRata = produk.stream()
.mapToInt(Produk::harga)
.average()
.orElse(0);
System.out.println("Rata-rata harga : " + rataRata);
}
}Output yang Dihasilkan:
Total harga minuman : 130000
Rata-rata harga : 70000.0Bedah singkatnya sebagai berikut:
- Baris 16-19 (
filter+mapToInt+sum): hanya produk minuman yang diambil, fieldhargadipetakan menjadiIntStream, lalusum()menjumlahkannya — 85000 + 45000 = 130000 — tanpa variabel akumulator dan tanpa pembungkusanInteger. - Baris 23-26 (
average()): rata-rata menghasilkanOptionalDoublekarena stream kosong tidak punya rata-rata;orElse(0)memberikan nilai cadangan0.0jika datanya kosong. Empat harga berjumlah 280000, sehingga rata-ratanya 70000.0. - Baris 18 (
mapToInt(Produk::harga)): method reference ke accessorint— tipe kembalian yang memang pas untukIntStream.mapToLongdanmapToDoublemenyediakan jalur serupa untuklongdandouble.
Kita akan mengulik IntStream dan kawan-kawannya secara penuh pada Chapter Primitive Streams. Data yang telah tersaring dan dipetakan kini tinggal ditata: Chapter sorted dan distinct menunjukkan cara mengurutkan elemen serta membuang duplikat di dalam pipeline.
Referensi #
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
HitungOmzet.java
Unduh berkasimport java.util.List;
public class HitungOmzet {
record Produk(String nama, String kategori, int harga) {}
public static void main(String[] args) {
List<Produk> produk = List.of(
new Produk("Kopi Arabika", "minuman", 85000),
new Produk("Teh Hijau", "minuman", 45000),
new Produk("Roti Gandum", "makanan", 30000),
new Produk("Madu Hutan", "makanan", 120000)
);
// mapToInt: field harga menjadi IntStream, lalu dijumlahkan
int totalMinuman = produk.stream()
.filter(p -> p.kategori().equals("minuman"))
.mapToInt(Produk::harga)
.sum();
System.out.println("Total harga minuman : " + totalMinuman);
// Rata-rata harga seluruh produk
double rataRata = produk.stream()
.mapToInt(Produk::harga)
.average()
.orElse(0);
System.out.println("Rata-rata harga : " + rataRata);
}
}
MeratakanData.java
Unduh berkasimport java.util.Arrays;
import java.util.List;
public class MeratakanData {
record Pesanan(String pelanggan, List<String> item) {}
public static void main(String[] args) {
// Struktur bersarang List<List<String>> diratakan dengan List::stream
List<List<String>> keranjangPerHari = List.of(
List.of("Kopi", "Roti"),
List.of("Teh", "Madu", "Gula"),
List.of("Air")
);
List<String> semuaItem = keranjangPerHari.stream()
.flatMap(List::stream)
.toList();
System.out.println("Rata satu tingkat : " + semuaItem);
// Record berisi List: item lintas pesanan digabung
List<Pesanan> pesanan = List.of(
new Pesanan("Budi", List.of("Kopi", "Roti")),
new Pesanan("Siti", List.of("Teh")),
new Pesanan("Dewi", List.of("Madu", "Gula"))
);
List<String> itemTerjual = pesanan.stream()
.flatMap(p -> p.item().stream())
.toList();
System.out.println("Item terjual : " + itemTerjual);
// List<int[]> diratakan dengan Arrays::stream -> IntStream
List<int[]> stokPerRak = List.of(new int[]{10, 20}, new int[]{5, 8, 12});
int totalStok = stokPerRak.stream()
.flatMapToInt(Arrays::stream)
.sum();
System.out.println("Total stok : " + totalStok);
// Teks multi-baris -> daftar kata dengan lines() lalu flatMap
String deskripsi = "Kopi Arabika Premium\nBerasal dari Gayo";
List<String> kata = deskripsi.lines()
.flatMap(baris -> Arrays.stream(baris.split(" ")))
.toList();
System.out.println("Kata : " + kata);
}
}
TransformasiMap.java
Unduh berkasimport java.util.List;
public class TransformasiMap {
record Produk(String nama, String kategori, int harga) {}
public static void main(String[] args) {
List<Produk> produk = List.of(
new Produk("Kopi Arabika", "minuman", 85000),
new Produk("Teh Hijau", "minuman", 45000),
new Produk("Roti Gandum", "makanan", 30000)
);
// Objek -> field: Produk dipetakan menjadi nama produk
List<String> nama = produk.stream().map(Produk::nama).toList();
System.out.println("Nama : " + nama);
// Transformasi teks: setiap nama menjadi huruf besar
List<String> namaBesar = produk.stream()
.map(p -> p.nama().toUpperCase())
.toList();
System.out.println("Huruf besar: " + namaBesar);
// Transformasi tipe: String menjadi panjang karakternya
List<Integer> panjangNama = nama.stream().map(String::length).toList();
System.out.println("Panjang : " + panjangNama);
}
}