Java Stream API diperkenalkan pada rilis JDK 8 dengan fungsi utama untuk mempermudah pemrosesan elemen-elemen data secara kolektif menggunakan operasi-operasi fungsional. Java Stream API disediakan pada paket java.util.stream. Selain bisa digunakan secara independen, API ini juga bisa diakses melalui Java Collection API.
Dalam stream API, terdapat 4 interface inti yaitu : IntStream
, LongStream
, DoubleStream
dan Stream<T>
. Sesuai namanya, masing-masing stream digunakan untuk elemen dengan tipe Integer
, Long
, dan Double
terkecuali interface Stream<T>
.
Terdapat beberapa metode statik untuk mendapatkan stream yang disediakan dalam masing-masing interface, beberapa diantaranya memiliki nama yang sama akan tetapi menggunakan argumen dan menghasilkan stream dengan tipe yang berbeda. Metode yang akan dibahas disini antara lain of()
, generate()
, dan iterate()
.
Metode of()
tersedia pada masing-masing interface, dengan cara pemanggilan yang kurang lebih sama, yaitu dengan menyebutkan keseluruhan elemen yang akan diproses sebagai parameter pemanggilan metode.
IntStream str2 = IntStream.of(1, 2, 8, 6, 5, 4, 3);
Kode diatas akan menghasilkan stream dengan tipe integer. Untuk tipe lainnya, digunakan interface Stream<T>
:
Stream<String> str3 = Stream.of("quick", "brown", "fox");
Contoh lain, semisal kita tentukan class Siswa
dengan struktur berikut :
class Siswa {
// properties ...
public Siswa(String nama, int umur, int berat, int tinggi) {
this.nama = nama;
this.umur = umur;
this.berat = berat;
this.tinggi = tinggi;
}
// getters, setters, etc ...
@Override
public String toString() {
String fmt = "Nama : %s, umur: %s tahun, tinggi: %s cm, berat: %s kg";
return String.format(fmt, nama, umur, tinggi, berat);
}
}
Kita bisa dapatkan stream dengan tipe Siswa
dengan menggunakan kode seperti dibawah ini :
Stream<Siswa> str = Stream
.of(
new Siswa("budi", 18, 165, 178),
new Siswa("imam", 17, 167, 169)
);
Metode generate()
digunakan untuk mendapatkan stream dengan nilai elemen tidak terbatas yang dibangkitkan menggunakan interface Supplier
. Contoh penggunaannya adalah sebagai berikut :
DoubleStream str = DoubleStream.generate(() -> {
return new Random().nextDouble();
});
Berikut ini contoh penggunaan metode generate()
untuk mendapatkan stream dengan tipe String
Stream<String> stream = Stream.generate(() -> {
String t = "";
for(int i=0; i<128; i++) {
t += (char)(new Random().nextInt(126 - 33) + 33);
}
return t;
});
Metode iterate()
digunakan untuk mendapatkan stream dengan nilai elemen tidak terbatas, yang dibangkitkan menggunakan fungsi f
yang bekerja terhadap paramater seed
, dengan pola seed
, f(seed)
, f(f(seed))
, dan seterusnya.
Metode ini menggunakan dua parameter yaitu :
Parameter seed
dengan tipe yang berbeda untuk tiap interface. Pada interface Stream<T>
, parameter ini akan menggunakan tipe T
, sedangkan pada interface lainnya, parameter ini akan menggunakan tipe int
, long
atau double
.
Fungsi f
yang merepresentasikan operasi dengan operand tunggal (unary operator). Fungsi ini memiliki tipe yang berbeda untuk tiap-tiap interface. Pada interface Stream<T>
fungsi ini adalah interface UnaryOperator<T>
, sedangkan pada interface lain, fungsi ini adalah IntUnaryOperator
, LongUnaryOperator
atau DoubleUnaryOperator
.
Contoh penggunaan metode ini adalah sebagai berikut :
IntStream stream = IntStream.iterate(1, (i) -> 2 * i);
Stream ini akan berisi nilai elemen 1, 2, 4, 8, 16, 32 dan seterusnya. Contoh lain penggunaan metode ini adalah sebagai berikut :
Stream<String> stream = Stream.iterate("text", (s) -> String.format("f(%s)", s));
Selain dibentuk secara independen, stream juga bisa didapatkan dari Collection API. Disini kita cukup memanggil metode stream()
yang akan mengembalikan Stream<E>
, dimana E
adalah tipe dari elemen Collection
. Sebagai contoh :
List<Siswa> list = Arrays.asList(
new Siswa("budi", 15, 168, 177),
new Siswa("rina", 16, 166, 156)
);
Stream<Siswa> stream = list.stream();
Setelah membahas tentang bagaimana mendapatkan stream, disini kita akan lanjutkan dengan membahas tentang bagaimana menggunakan stream tersebut. Stream API menyediakan beragam metode yang secara umum terbagi menjadi dua kelompok yaitu intermediate dan terminal.
Metode forEach()
akan mengerjakan aksi tertentu terhadap tiap elemen stream. Pemanggilan terhadap metode ini disertai dengan argumen berupa implementasi interface Consumer<? super T>
yang mendefinisikan "aksi" yang akan dikerjakan
IntStream
.of(1, 5, 2, 7, 3, 9, 4, 8)
.forEach((i) -> {
if((i % 2) == 0) {
System.out.println(i);
}
});
Kode diatas membentuk stream dengan nilai-nilai elemen 1, 5, 2, 7 dan seterusnya. Didalam metode forEach()
didefinisikan sebuah fungsi yang akan mengevaluasi nilai elemen stream i
dan akan menampilkan nilai tersebut jika hasil evaluasi bernilai true
.
Metode count()
berfungsi untuk mendapatkan jumlah elemen didalam stream, sedangkan metode sum()
berfungsi untuk mendapatkan total dari keseluruhan nilai elemen stream. Metode sum()
hanya tersedia pada stream numerik.
int count = Stream.of("quick", "brown", "fox").count(); // 3
double sum = DoubleStream.of(3.5, 4.1, 7.2, 8.8).sum(); // 23.6
Sesuai namanya ketiga metode max()
, min()
, dan average()
akan mengembalikan nilai terbesar, terkecil dan rata-rata dari sejumlah elemen didalam stream. Ketiganya adalah metode terminal yang akan mengembalikan tipe Optional<T>
, OptionalInt
, OptionalLong
atau OptionalDouble
sesuai dengan interface stream yang digunakan. Dari objek Optional
tersebut, kita bisa dapatkan nilai yang dicari dengan menggunakan metode get()
, getAsInt()
, getAsLong()
atau getAsDouble()
.
int[] values = {3, 1, 5, 2, 7, 9, 4, 6, 8};
int max = Arrays.stream(values).max().getAsInt();
int min = Arrays.stream(values).min().getAsInt();
double avg = Arrays.stream(values).average().getAsDouble();
Pada interface Stream<T>
, metode max()
dan min()
dapat dipanggil dengan menyebutkan implementasi Comparator<T>
sebagai argumen metode, untuk mendapatkan nilai terbesar atau terkecil dari suatu atribut elemen dengan tipe T
. Sebagai contoh, kita ingin mendapatkan data siswa yang paling tua dibandingkan siswa lainnya
Siswa s = Arrays
.asList(
new Siswa("rina", 17, 150, 168),
new Siswa("merry", 15, 167, 177),
new Siswa("budi", 18, 165, 171),
new Siswa("ahmad", 16, 188, 150)
)
.stream()
.max((e1, e2) -> {
return e1.getUmur() - e2.getUmur();
})
.get();
Metode distinct()
adalah metode intermediate yang akan menghasilkan stream baru dengan nilai-nilai elemen yang unik dengan menghilangkan elemen-elemen ganda didalam stream.
IntStream
.of(1,4,2,3,4,1,2,7,8,9)
.distinct()
.forEach(i -> System.out.print(i + " ")); // 1 4 2 3 7 8 9
Metode sorted()
adalah metode intermediate yang menghasilkan stream baru dengan nilai elemen yang tersusun secara berurutan. Contoh penggunaannya adalah sebagai berikut :
IntStream
.of(1,4,2,3,4,1,2,7,8,9)
.distinct()
.sorted()
.forEach(System.out::println); // 1 2 3 4 7 8 9
Pada interface Stream<T>
, metode ini dapat dipanggil dengan menggunakan implementasi Comparator<T>
untuk mengurutkan elemen objek dengan tipe T
berdasarkan atribut tertentu. Sebagai contoh, kita ingin mengurutkan data siswa berdasarkan umur.
Arrays
.asList(
new Siswa("budi", 18, 165, 171),
new Siswa("rina", 17, 150, 168),
new Siswa("merry", 15, 167, 177),
new Siswa("ahmad", 16, 188, 150)
)
.stream()
.sorted((e1, e2) -> {
return e1.getUmur() - e2.getUmur();
})
.forEach(System.out::println);
Metode filter()
adalah metode intermediate yang akan menyeleksi elemen-elemen stream berdasarkan kriteria yang ditentukan. Metode ini menggunakan argumen berupa implementasi Predicate<T>
, IntPredicate
. DoublePredicate
atau LongPredicate
disesuaikan dengan interface stream yang digunakan.
IntStream
.of(24, 15, 22, 72, 34, 69, 21)
.filter((i) -> (i % 2) == 0)
.sorted()
.forEach(System.out::println); // 22, 24, 34, 72
Contoh lain, kita ingin mendapatkan data siswa yang berumur lebih dari 16 tahun
Arrays
.asList(
new Siswa("budi", 18, 165, 171),
new Siswa("rina", 17, 150, 168),
new Siswa("merry", 15, 167, 177),
new Siswa("ahmad", 16, 188, 150)
)
.stream()
.filter((e) -> e.getUmur() > 16)
.forEach(System.out::println);