Stream API

Stream API, Java 8 ile eklenmiş bir yeniliktir. Stream akıntı anlamına gelir ve çalışma mantığı olarak da bir akıntı üzerine gelen verileri sırayla ya da paralel işleyen metotlar olarak düşünülebilir. Stream sayesinde loop kullanan imperative metotlar, fonksiyonel programlamaya uygun şekilde değiştirilebilmektedir.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Main {
public static void main(String args[]) {
List < Integer > integerList = Arrays.asList(1, 2, 3, 4, 5);
int result = 0;
for (int i = 0; i < integerList.size(); i++) {
result += integerList.get(i) * 2;
}
System.out.println(result);
result = integerList.stream()
.map(i > i * 2)
.reduce(0, Integer::sum);
System.out.println(result);
Bu örnekte :: sembolünün Integer.sum() metodu referansı olarak da kullanılması gösterilmiştir. Loop kullanırken sürekli değeri güncellenen result kullanarak, stream örneğinde immutable mantığına da uyum sağlamıştır. Bu örnekte alınan çıktı ikisi için de aynı olacaktı fakat, toplamaya 0’dan değil de farklı bir sayıdan başlanması istenseydi result değerine başka bir sayı atamanın karşılığı stream için reduce içindeki 0’ı artırmak olacaktı. Stream’in başlıca işlevlerinden aşağıda kısaca bahsedilmiştir:
Map
Map operatörü stream içindeki her eleman üzerinde belirtilmiş fonksiyonu çağırarak yeni bir stream yaratır. Yukarıdaki örnekte 1, 2, 3, 4, 5 elemanlarını bulunduran streamden 2, 4, 6, 8, 10 elemanlarını bulunduran yeni bir stream yarattı.
Filter
Filter operatörü stream içindeki verileri belirtilmiş koşula göre test ederek, sadece uyan verilere sahip yeni bir stream yaratır. Örneğin, aşağıdaki örnekte filter sonucundaki stream sadece çift sayıları bulunduran {2,4} olacaktır:
1
2
3
4
5
List < Integer > integerList = Arrays.asList(1, 2, 3, 4, 5);
List < Integer > streamedList = integerList.stream()
.filter(i -> i % 2 == 0)
.collect(Collectors.toList());
Collect
Stream verilerini farklı şekillerde gruplayıp belirtilen birimde dönen operatördür. Önceki örnekte liste olarak dönmesi söylenmiştir.
ForEach
ForEach bir consumerdır ve streamde bulunan her veri üzerinde forEach içinde belirtilmiş işlevi çağırır. Örneğin aşağıda listenin her elemanı için, elemanın iki katını yazdıran bir kullanım mevcuttur.
1
2
3
List<Integer> integerList = Arrays.asList(1,2,3);
integerList.forEach(integer -> System.out.println(integer * 2));
Program çıktısı:
1
2
3
2
4
6
AllMatch
AllMatch operatörü stream içindeki verileri belirtilmiş koşula göre test eder ve eğer tüm veriler koşulu sağlıyorsa true, herhangi biri bile sağlamıyorsa false döner.
Örneğin aşağıda yer alan sayiList listesinde tek sayılar da bulunduğu için allMatch(çift sayı mı) kontrolü false dönecektir. Fakat ciftSayiList içindeki tüm sayılar çift olduğu için true dönecektir.
1
2
3
4
5
List<Integer> sayiList = Arrays.asList(1,2,3);
List<Integer> ciftSayiList = Arrays.asList(2,4,6);
System.out.println(sayiList.stream().allMatch(sayi -> sayi % 2 == 0));
System.out.println(ciftSayiList.stream().allMatch(sayi -> sayi % 2 == 0));
AnyMatch
AnyMatch operatörü stream içindeki verileri belirtilmiş koşula göre test eder ve eğer hiçbir veri koşulu sağlamıyorsa false, herhangi biri bile sağlıyorsa true döner.
4.5’teki örneğin bir benzerini burada da kullanılabilir. Fakat burada elemanlardan herhangi birinin koşulu sağlaması yeterli olduğu için sayiList ve ciftSayiList true, içinde hiç çift sayı bulundurmayan tekSayiList false dönecektir.
1
2
3
4
5
6
7
List<Integer> sayiList = Arrays.asList(1,2,3);
List<Integer> ciftSayiList = Arrays.asList(2,4,6);
List<Integer> tekSayiList = Arrays.asList(1, 3, 5);
System.out.println(sayiList.stream().anyMatch(sayi -> sayi % 2 == 0));
System.out.println(ciftSayiList.stream().anyMatch(sayi -> sayi % 2 == 0));
System.out.println(tekSayiList.stream().anyMatch(sayi -> sayi % 2 == 0));
Distinct
Stream içindeki verileri birbiriyle karşılaştırarak, eşitlik koşulunu sağlayan çoklu verileri teke indirger. Örneğin aşağıdaki sayiList içinde tekrar eden elemanlar bulunmaktadır. Eğer distinct kullanmadan forEach kullanıp printleseydi her eleman için bir tane olmak üzere yedi adet çıktı olacaktı. Fakat önce distinct elemanları alınıp kullanıldığı zaman tekrar eden elemanları çıkartıp, geriye kalan (1, 2, 3, 4) listesi üzerinde işlem yapacak ve dört adet çıktı verecektir.
1
2
3
List<Integer> sayiList = Arrays.asList(1,2,3,3,1,3,4);
sayiList.stream().distinct().forEach(sayi -> System.out.println(sayi));
Program çıktısı:
1
2
3
4
1
2
3
4
Count
Stream içindeki veri sayısını döner.
4.7’de bulunan örnek buraya da uyarlanabilir. Program ilk önce listenin orijinal halinin eleman sayısını ardından da distinct çağrısından sonraki eleman sayısını çıktı olarak verecektir.
1
2
3
4
List<Integer> sayiList = Arrays.asList(1,2,3,3,1,3,4);
System.out.println(sayiList.stream().count());
System.out.println(sayiList.stream().distinct().count());
Program çıktısı:
1
2
7
4
Min/Max
Sırasıyla stream içindeki en küçük ve en büyük veriyi döner. Karşılaştırma metodu özel olarak tanımlanabilir.
Örneğin aşağıdaki liste sırasıyla 1 ve 5 çıktısı verecektir.
1
2
3
4
List<Integer> sayiList = Arrays.asList(3, 1, 4, 5, 2);
System.out.println(sayiList.stream().min((x, y) -> x.compareTo(y)).get());
System.out.println(sayiList.stream().max((x, y) -> x.compareTo(y)).get());
FlatMap
FlatMap operatörü map’e oldukça benzer olmakla birlikte farkı, kendi içinde streamleri otomatik olarak birleştirip tek bir stream haline getirmesidir. Buna düzleştirme (flatten) denir.
1
2
3
4
5
6
7
8
9
10
11
12
13
public class Main {
public static void main(String args[]) {
List < List < Integer >> integerList = Arrays.asList(Arrays.asList(1),
Arrays.asList(2), Arrays.asList(3));
List < Integer > streamedList = integerList.stream()
.flatMap(list -> list.stream())
.toList()
System.out.println(streamedList);
}
}
Örneğin, burada liste içindeki liste elemanlarının stream’i kolaylıkla tek bir listeye toplanabilmiştir.
Parallel
Normalde stream tek sıra halinde çalışır. Stream paralel yapıldığında ise mevcut stream paralel olarak aynı anda çalışacak küçük streamlere bölünür. Paralellik dolayısıyla verileri işleme sırası karışabilir. Bu sebeple paralel stream kullanılacağı zaman işlem sırasının değişkenliğinin sorun yaratmayacağından emin olunması gerekir.
Aşağıdaki örnekte aynı liste üzerinde önce stream oluşturulup sonradan paralele çevirme ve en baştan paralel stream oluşturma şeklinde iki kullanım görülebilir. Burada sadece, her bir stream için konsola 1, 2, 3, 4 ve 5’in basılacağından emin olunabilir. Hangi sırayla basılacağı değişkenlik gösterecektir. İki stream’in sırası bile birbirinden farklı olabilir. Örneğin birisi 3, 5, 4, 2, 1 sırasıyla çıktı verirken diğeri 3, 2, 4, 1, 5 şeklinde verebilir.
1
2
3
4
List<Integer> sayiList = Arrays.asList(1, 2, 3, 4, 5);
sayiList.parallelStream().forEach(i -> System.out.println(i));
sayiList.stream().parallel().forEach(i -> System.out.println(i));