Pipeline yang kita bangun sejauh ini mengalirkan objek: Siswa, Produk, String. Masalah muncul saat data berupa angka biasa — int, long, double — karena Stream<T> hanya menerima objek, sehingga setiap angka harus dibungkus menjadi Integer, Long, atau Double melalui proses boxing. Untuk deret besar, pembungkusan ini menambah beban memori dan waktu tanpa memberi manfaat apa pun. Java menjawabnya dengan tiga stream khusus: IntStream, LongStream, dan DoubleStream, lengkap dengan operasi statistik bawaan yang tidak dimiliki Stream<T>.
Boxing dan Tiga Stream Primitif #
Generics di Java bekerja di atas objek, bukan tipe primitif; List<Integer> tidak bisa menyimpan int secara mentah. Ketika Stream<Integer> menjalankan filter, setiap perbandingan mengharuskan JIT membuka kotak (Integer menjadi int) lalu menutupnya kembali untuk hasil antara. Pada deret sejuta angka, bolak-balik ini menjadi pekerjaan yang sebenarnya tidak diperlukan.
IntStream menyimpan nilai int dalam struktur internal berbasis array primitif, sehingga tidak ada kotak sama sekali. Sebagai gantinya, stream primitif tidak bisa membawa objek apa pun — itulah sebabnya Java menyediakan jalur konversi seperti mapToObj dan boxed. Perbandingan ringkasnya:
| Aspek | Stream<Integer> | IntStream |
|---|---|---|
| Isi elemen | objek Integer hasil boxing | nilai int mentah |
| Operasi statistik bawaan | tidak ada | sum, min, max, average, summaryStatistics |
| Beban memori | satu objek per angka | array primitif yang rapat |
| Cocok untuk | campuran objek-angka | komputasi numerik murni |
Saudara kandungnya mengikuti pola yang sama: LongStream untuk long dan DoubleStream untuk double. Seluruh method yang kita bahas di bawah (range, sum, summaryStatistics) punya padanan langsung pada ketiganya.
Deret Angka dan Statistik Bawaan #
Dua sumber deret paling populer adalah range dan iterate. range(a, b) menghasilkan angka dari a sampai sebelum b — batas atas tidak ikut — sedangkan rangeClosed(a, b) menyertakan batas atas. iterate(nilaiAwal, fungsiLanjutan) membangkitkan angka berikutnya dari fungsi, sehingga wajib dipagung limit. Program berikut mencoba keduanya lalu menghitung lima statistik deret 1 sampai 100:
import java.util.IntSummaryStatistics;
import java.util.stream.IntStream;
public class StatistikDeret {
public static void main(String[] args) {
// range: batas atas tidak ikut; rangeClosed: batas atas ikut
System.out.println("range(1, 6) = " + IntStream.range(1, 6).boxed().toList());
System.out.println("rangeClosed(1, 5)= " + IntStream.rangeClosed(1, 5).boxed().toList());
// iterate: nilai awal + fungsi lanjutan, dipagung limit
System.out.println("pangkat dua = " + IntStream.iterate(2, n -> n * 2).limit(6).boxed().toList());
// lima operasi statistik bawaan pada deret 1..100
System.out.println("sum = " + IntStream.rangeClosed(1, 100).sum());
System.out.println("min = " + IntStream.rangeClosed(1, 100).min());
System.out.println("max = " + IntStream.rangeClosed(1, 100).max());
System.out.println("average = " + IntStream.rangeClosed(1, 100).average());
System.out.println("count = " + IntStream.rangeClosed(1, 100).count());
// summaryStatistics: seluruh angka penting dalam satu penelusuran
IntSummaryStatistics stat = IntStream.rangeClosed(1, 100).summaryStatistics();
System.out.println("statistik = " + stat);
}
}Output yang Dihasilkan:
range(1, 6) = [1, 2, 3, 4, 5]
rangeClosed(1, 5)= [1, 2, 3, 4, 5]
pangkat dua = [2, 4, 8, 16, 32, 64]
sum = 5050
min = OptionalInt[1]
max = OptionalInt[100]
average = OptionalDouble[50.5]
count = 100
statistik = IntSummaryStatistics{count=100, sum=5050, min=1, average=50.500000, max=100}Mari kita telaah baris-baris kode di atas:
- Baris 8-9 (
rangedanrangeClosed): keduanya mencetak[1, 2, 3, 4, 5]karena batas yang dipilih sengaja disetarakan —range(1, 6)berhenti sebelum 6,rangeClosed(1, 5)sampai di 5. Kedua stream diakhiriboxed().toList()agar bisa dicetak sebagai daftar. - Baris 12 (
iterate(2, n -> n * 2).limit(6)): nilai awal 2 dikalikan dua berulang kali menjadi 2, 4, 8, 16, 32, 64. Tanpalimit,iteratetidak pernah berhenti karena stream primitif ini tak berujung. - Baris 15 (
sum()): mengembalikanintlangsung — bukanOptionalInt— karena deret kosong punya jawaban yang pasti, yaitu 0. - Baris 16-18 (
min(),max(),average()): ketiganya menghasilkanOptionalIntatauOptionalDoublekarena deret kosong tidak punya nilai terkecil, terbesar, maupun rata-rata. Output menampilkan wadahnya apa adanya:OptionalInt[1],OptionalInt[100],OptionalDouble[50.5]. - Baris 22 (
summaryStatistics()): satu objekIntSummaryStatisticsmemuat kelima angka sekaligus. Bila laporan membutuhkan total, rata-rata, dan nilai ekstrem dari sumber yang sama,summaryStatisticsmenelusuri stream hanya sekali — lebih hemat daripada memanggilsum()laluaverage()lalumax()secara terpisah.
Konversi Antar Stream dengan chars, mapToObj, dan boxed #
Teks adalah sumber angka primitif yang mengejutkan seringnya: method String.chars() mengembalikan IntStream berisi kode Unicode setiap karakter. Dari titik itu, seluruh peralatan IntStream langsung terpakai. Sebaliknya, saat hasil akhir berupa objek, kita keluar dari dunia primitif lewat mapToObj atau boxed:
import java.util.stream.Collectors;
public class AngkaDariTeks {
public static void main(String[] args) {
String nama = "Budi Santoso";
// chars(): IntStream berisi kode Unicode tiap karakter
long jumlahHuruf = nama.chars()
.filter(c -> !Character.isWhitespace(c))
.count();
System.out.println("Jumlah huruf (tanpa spasi) = " + jumlahHuruf);
long jumlahVokal = nama.chars()
.filter(c -> "aeiouAEIOU".indexOf(c) >= 0)
.count();
System.out.println("Jumlah vokal = " + jumlahVokal);
// mapToObj: ubah int menjadi objek String agar bisa di-join
String dipisah = nama.chars()
.mapToObj(c -> String.valueOf((char) c))
.collect(Collectors.joining("-"));
System.out.println("Per karakter = " + dipisah);
// boxed: bungkus int menjadi Integer untuk kebutuhan Collection
System.out.println("Kode 4 huruf pertama = " + nama.chars().limit(4).boxed().toList());
}
}Output yang Dihasilkan:
Jumlah huruf (tanpa spasi) = 11
Jumlah vokal = 5
Per karakter = B-u-d-i- -S-a-n-t-o-s-o
Kode 4 huruf pertama = [66, 117, 100, 105]Mari kita telaah baris-baris kode di atas:
- Baris 9-11 (
chars().filter(...).count()):chars()mengalirkan kode karakter,filtermembuang spasi lewatCharacter.isWhitespace, dancountmenghitung sisanya — 11 huruf dari"Budi Santoso". - Baris 14-16 (
"aeiouAEIOU".indexOf(c) >= 0): filter ini menguji keanggotaan dengan memanfaatkanindexOfyang mengembalikan -1 bila tidak ketemu; teknik ringkas untuk uji “apakah c termasuk salah satu dari daftar ini”. - Baris 20-22 (
mapToObj(c -> String.valueOf((char) c))):mapToObjkeluar dariIntStreammenujuStream<String>. Pemindahan(char) cwajib karena tanpa pemindahan,String.valueOf(c)menghasilkan angka, bukan huruf. - Baris 26 (
boxed()):boxedmembungkus setiapintmenjadiIntegeragar hasilnya bisa menjadiList<Integer>; kode hurufB,u,d,itampil sebagai 66, 117, 100, 105.
mapToDouble dan flatMapToInt untuk Data Toko #
Arah sebaliknya juga tersedia: stream objek berubah menjadi stream primitif lewat mapToDouble, mapToInt, atau mapToLong — biasanya untuk memanen satu field numerik lalu menghitung. Sementara flatMapToInt melebur beberapa deret primitif menjadi satu:
import java.util.Arrays;
import java.util.List;
import java.util.stream.IntStream;
public class KeranjangBelanja {
record Produk(String nama, double harga) {}
public static void main(String[] args) {
List<Produk> keranjang = List.of(
new Produk("Pena", 2500.0),
new Produk("Buku Tulis", 5000.0),
new Produk("Penghapus", 1500.5)
);
// mapToDouble: stream objek berubah menjadi DoubleStream
double total = keranjang.stream().mapToDouble(Produk::harga).sum();
System.out.printf("Total belanja = Rp%.2f%n", total);
double rataRata = keranjang.stream().mapToDouble(Produk::harga)
.average().orElse(0.0);
System.out.printf("Rata-rata = Rp%.2f%n", rataRata);
// flatMapToInt: lebur beberapa deret int menjadi satu stream
int[][] stokCabang = { {12, 8, 5}, {20, 4}, {7} };
int[] seluruhStok = Arrays.stream(stokCabang)
.flatMapToInt(IntStream::of)
.toArray();
System.out.println("Stok gabungan = " + Arrays.toString(seluruhStok));
System.out.println("Total stok = " + Arrays.stream(stokCabang)
.flatMapToInt(IntStream::of).sum());
}
}Output yang Dihasilkan:
Total belanja = Rp9000.50
Rata-rata = Rp3000.17
Stok gabungan = [12, 8, 5, 20, 4, 7]
Total stok = 56Mari kita telaah baris-baris kode di atas:
- Baris 17 (
keranjang.stream().mapToDouble(Produk::harga).sum()):mapToDoublememanen fieldhargabertipedouble, menghasilkanDoubleStreamyang langsung punyasum(). Total Rp9000.50 tercetak lewatprintfformat%.2fagar rapi dua angka desimal. - Baris 20-21 (
average().orElse(0.0)): rata-rataDoubleStreamberupaOptionalDouble;orElse(0.0)menyiapkan nilai cadangan untuk keranjang kosong tanpa memanggil methodget()langsung. - Baris 26-28 (
Arrays.stream(stokCabang).flatMapToInt(IntStream::of)):Arrays.stream(int[][])menghasilkanStream<int[]>— stream dari larik, bukan angka.flatMapToInt(IntStream::of)mengubah setiap larik menjadiIntStreamlalu meratakannya menjadi satu aliran angka, sehinggatoArray()menghasilkan[12, 8, 5, 20, 4, 7]dansum()menghasilkan 56.
Statistik bawaan stream primitif sering menghasilkan OptionalInt dan OptionalDouble. Wadah Optional itu bukan aksesoris — stream memang butuh cara menyatakan “tidak ada hasil”. Chapter berikutnya membedah Optional secara menyeluruh: cara membuatnya, mengalirkannya lewat map dan filter, serta perbedaan penting antara orElse dan orElseGet.
Referensi #
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
AngkaDariTeks.java
Unduh berkasimport java.util.stream.Collectors;
public class AngkaDariTeks {
public static void main(String[] args) {
String nama = "Budi Santoso";
// chars(): IntStream berisi kode Unicode tiap karakter
long jumlahHuruf = nama.chars()
.filter(c -> !Character.isWhitespace(c))
.count();
System.out.println("Jumlah huruf (tanpa spasi) = " + jumlahHuruf);
long jumlahVokal = nama.chars()
.filter(c -> "aeiouAEIOU".indexOf(c) >= 0)
.count();
System.out.println("Jumlah vokal = " + jumlahVokal);
// mapToObj: ubah int menjadi objek String agar bisa di-join
String dipisah = nama.chars()
.mapToObj(c -> String.valueOf((char) c))
.collect(Collectors.joining("-"));
System.out.println("Per karakter = " + dipisah);
// boxed: bungkus int menjadi Integer untuk kebutuhan Collection
System.out.println("Kode 4 huruf pertama = " + nama.chars().limit(4).boxed().toList());
}
}
KeranjangBelanja.java
Unduh berkasimport java.util.Arrays;
import java.util.List;
import java.util.stream.IntStream;
public class KeranjangBelanja {
record Produk(String nama, double harga) {}
public static void main(String[] args) {
List<Produk> keranjang = List.of(
new Produk("Pena", 2500.0),
new Produk("Buku Tulis", 5000.0),
new Produk("Penghapus", 1500.5)
);
// mapToDouble: stream objek berubah menjadi DoubleStream
double total = keranjang.stream().mapToDouble(Produk::harga).sum();
System.out.printf("Total belanja = Rp%.2f%n", total);
double rataRata = keranjang.stream().mapToDouble(Produk::harga)
.average().orElse(0.0);
System.out.printf("Rata-rata = Rp%.2f%n", rataRata);
// flatMapToInt: lebur beberapa deret int menjadi satu stream
int[][] stokCabang = { {12, 8, 5}, {20, 4}, {7} };
int[] seluruhStok = Arrays.stream(stokCabang)
.flatMapToInt(IntStream::of)
.toArray();
System.out.println("Stok gabungan = " + Arrays.toString(seluruhStok));
System.out.println("Total stok = " + Arrays.stream(stokCabang)
.flatMapToInt(IntStream::of).sum());
}
}
StatistikDeret.java
Unduh berkasimport java.util.IntSummaryStatistics;
import java.util.stream.IntStream;
public class StatistikDeret {
public static void main(String[] args) {
// range: batas atas tidak ikut; rangeClosed: batas atas ikut
System.out.println("range(1, 6) = " + IntStream.range(1, 6).boxed().toList());
System.out.println("rangeClosed(1, 5)= " + IntStream.rangeClosed(1, 5).boxed().toList());
// iterate: nilai awal + fungsi lanjutan, dipagung limit
System.out.println("pangkat dua = " + IntStream.iterate(2, n -> n * 2).limit(6).boxed().toList());
// lima operasi statistik bawaan pada deret 1..100
System.out.println("sum = " + IntStream.rangeClosed(1, 100).sum());
System.out.println("min = " + IntStream.rangeClosed(1, 100).min());
System.out.println("max = " + IntStream.rangeClosed(1, 100).max());
System.out.println("average = " + IntStream.rangeClosed(1, 100).average());
System.out.println("count = " + IntStream.rangeClosed(1, 100).count());
// summaryStatistics: seluruh angka penting dalam satu penelusuran
IntSummaryStatistics stat = IntStream.rangeClosed(1, 100).summaryStatistics();
System.out.println("statistik = " + stat);
}
}