02 Maret 2019
Mengenal Java Stream API

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>.

Membuat Stream

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

Menggunakan Metode Of

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

Menggunakan Metode Generate

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

Menggunakan Metode Iterate

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 :

  1. 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.

  2. 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));

Mendapatkan Stream dari Collection API

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

Menggunakan 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.

  1. Metode intermediate adalah metode yang mengembalikan stream baru yang berisi elemen hasil pemrosesan yang didefinisikan pada saat metode tersebut dipanggil. Metode intermediate bisa dirangkaikan (chain) dengan metode terminal atau metode intermediate lainnya.
  2. Metode terminal adalah metode yang akan mengembalikan hasil akhir dari pemrosesan elemen stream. Dengan memanggil metode ini diasumsikan elemen-elemen stream sudah selesai diproses dan stream sudah tidak bisa lagi digunakan.

Metode forEach

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 dan sum

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

Metode Max, Min, dan Average

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

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

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

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