Java 8 Yenilikleri – Bölüm 3

Java 8 Yenilikleri – Bölüm 3

Selamlar arkadaşlar, Java 8 yeniliklerinden bahsettiğimiz yazı serisinin 3.bölümüne geldik. Önceki bölümde yardımcı fonksiyonel arayüzlerden (Consumer, Predicate …) bahsetmiştik. Bu yazıda ise özellikle diziler ve Collection’lar üzerinde çeşitli işlemler yapabilmek için kullanabileceğimiz Java’nın bizim için sunmuş olduğu Stream API‘ı inceleyeceğiz.


Stream Nedir ?

Stream kelimesinin türkçesine baktığımızda “akış, nehir, akarsu” olarak belirtilmekte. Aslında bu şekilde düşündüğümüzde yapısını kolayca anlayabiliriz. Bir nehir var ve devamlı akmakta, akıp gittiği için stream içindeki elemanlar sadece bir kere ziyaret edilir. Aynı elemanı tekrar işleme dahil edebilmek için yeni bir stream/nehir oluşturmamız gerekmektedir. Bu akış genel itibariyle kullanmış olduğumuz işlemlerin türüne bağlı olacak şekilde bir boru hattı gibi düşünülebilir.

Stream API’da En Çok Kullanılan Metodlar

MetodAçıklama
forEachİterasyon
filterFiltreleme
mapDönüştürme
reduceİndirgeme
distinctTekilleştirme
sortedSıralama
limitLimitleme
countSayma
collectToplamak/Biriktirmek

Bu tabloda bulunan tüm metodları anlatamaya çalışacağım tabi bu tabloda bulunmayan Stream arayüzü içerisinde bulunan çok daha fazla metod bulunmakta. Buradan ulaşabilirsiniz.

Stream Nesnesi Nasıl Elde Edilir ?

Stream bir arayüz olduğundan dolayı doğrudan nesne oluşturmak mümkün değildir. Stream nesnesini elde etmenin birden fazla yolu var. Ancak bu makalede Collection API üzerinde çalışacağımız için ve Collection arayüzünden türeyen nesneler stream() ya da parallelStream() metodlarını çağırarak geriye Stream nesnesi dönebilmektedir.

Stream’in Hayat Döngüsü

Java’da Stream’in hayat döngüsünü 3 işleme ayırabiliriz.

  1. Dizi veya Collection gibi bir veri kaynağından stream kaynağı oluşturma.
  2. Bir stream’e filtreleme, sıralama, dönüştürme gibi ara işlemler uygulanıp tekrardan işlenebilecek kaynak oluşturulabilir. Bu sebeple birden fazla kez kullanılabilir, zincirleme işlemler yapılabilir.
  3. Elimizdeki kaynağa sonuç ürettirebilecek terminal işlemleri olarak adlandıran forEach, reduce gibi işlemler uygulanır. Stream’e hayat döngüsü boyunca yalnızca 1 adet terminal işlemi uygulanabilir.

Yukarıda bahsettiğimiz ara işlemler ve terminal işlemlerini gruplayalım.

Ara İşlemler (Intermediate)Terminal İşlemleri
filter(Predicate<T>)forEach, forEachOrdered
map(Function<T>)toArray
flatmap(Function<T>)reduce
sorted(Comparator<T>)collect
peek(Consumer<T>)min, max
distinct()count
limit(long n)anyMatch, allMatch, noneMatch
skip(long n)findAny, findFirst

Şimdi, en çok kullanılan metodları tek tek anlatmaya çalışacağım…


1. ForEach

En basit ve en çok kullanılan, Stream içindeki veriyi tek tek tüketmek için oluşturulmuş bir yapıdır. Terminal işlemi olduğundan dolayı stream artık tüketilmiş olarak kabul edilir ve artık kullanılamayacağı anlamına gelir.
Örnek üzerinde inceleyelim ;


2. Filter

Dizimiz veya Collection’ımız üzerinde bizim belirteceğimiz koşullar doğrultusunda filtreleme işlemi yapmamıza yarayan bir yapıdır. Predicate arayüzünden türüyen bir parametre bekler. Bu işlem sonrası belirttiğimiz koşula uygun olmayan elemanlar bir sonraki aşamada kullanılamazlar. Artık elimizde filtrelenmiş/süzülmüş/elenmiş bir veri vardır. Örneğe bakalım;


3. Map

Stream içerisinde erişilen her bir eleman üzerinde işlem yapmamıza ve başka elemanlara dönüştürmemize imkan sağlayan map metodu parametre olarak Function arayüzünden türetilmiş bir ifade beklemektedir. Örneğimize bakalım;


4. Reduce

Türkçe karşılığı indirgeme/azaltma olarak geçen reduce çeşitli şekillerde kullanılırken genel olarak map metoduyla birlikte kullanılmakta. Tek başına reduce kullanımında elemanları teker teker işler. Bir önceki adımda elde edilen sonuç, bir sonraki elemanla işlemle tutulur. Reduce metodunu çeşitli halleri vardır ben 2 parametre alan metodunu anlatacağım.

T reduce(T identity, BinaryOperator<T> accumulator);

Burada identity değeri ilk olarak işleme başlarken hangi değer ile başlaması gerektiğini ifade etmekte. Örnek üzerinden inceleyelim;


5. Map & Reduce

Yukarıda hem map işlemini hem de reduce işleminden bahsettik. Bu iki metod birlikte kullanıldıkta çok güçlü olmakta. Örneğin elimizde öğrencilerin isim ve not bilgisinin olduğu bir liste olsun. Bu listede notu 50 üzerinde olan öğrencilerin notlarının toplamını bulalım;


6. Distinct

Stream içerisinde bulunan ve tekrar eden elemanlar varsa bunları distinct metodu ile çıkartabilir ve elimizde tekrar etmeyen birbirinden farklı elemanlar barından bir veri setimiz kalır. Örneğin;


7. Sorted

Bazı durumlarda elimizde ki veri setini belirli bir parametreye göre sıralamak isteyebiliriz. 2 türü bulunmaktadır. Birinci hali parametre almaz küçükten büyüğe şekilde sıralar. İkinci formatı ise Comparator arayüzünden türediği için bizim belirlemiş olduğumuz parametreye göre sıralama işlemi yapar. Örneğin;


8. Limit

Elimizde var olan bir veri kaynağını, göstermek istediğimiz kadarını göstermemize yarayan, sınırlandıran bir metoddur. Parametre olarak vereceğimiz rakamla aslında Stream içerisinde dönen çok sayıda veri var ancak sen bize şu kadarını göster diyoruz. Örnek üzerinden bakacak olursak;


9. Count

Stream içerisinde bulunan toplam veri sayısını öğrenmemize yarar.


10. Collect

Collect metodu Stream türündeki nesneleri başka biçimdeki nesneye, veri yapısına dönüştürmek için kullanılır. Collector arayüzünden türeyen bir parametre beklemektedir. Bu parametre bilgisi ile istenilen türe dönüşüm sağlanabilir. Collector türünden arayüzler Collectors sınıfının içinde bulunan metodlar ile elde edilebilir. Örnek üzerinden inceleyelim;


11. AnyMatch & AllMatch & NoneMatch

Bu 3 metodda Predicate arayüzünden türemiş bir parametre bekler. İsimlerini türkçeye çevirdiğimizde aslında az çok anlayabiliyoruz. Şöyle ki;

  • AnyMath: Vereceğimiz şarta bağlı olarak Stream içerisinde gezinir ve herhangi bir elemanla eşleşme durumunda true dönecektir.
  • AllMatch: Verilen şarta göre Stream içerisindeki tüm elemanların bu şarta uyması durumunda true dönecektir.
  • NoneMatch: Şarta göre Stream içindeki hiç bir elemanın şartı sağlamaması durumunda true dönecektir.

Örnek üzerinden incelemeye çalışalım.


12. Paralel Stream

Stream bizlere paralel işlem yapabilme kabiliyeti ile performans açısından önemsenecek sonuçlar vermekte. Örnek olarak bir ülke listemiz olsun ve bu ülke listemizi forEach ile gezelim ama her elemanı gezerken 1 sn gecikme koyalım. Böylelikle paralel işlem yaptığımızda farklı bir thread’e görev devredilecek mi izleyelim;

Programı çalıştırdığımızda çıktı aşağıdaki gibi olacaktır. Toplam geçen süreye baktığımızda aradaki farkı görebiliyoruz. Bu örnekle stream’i paralel şekilde yürütebileceğimizi ve performansa etkisini bir nebzede olsa gözlemleyebildiğimizi söyleyebiliriz.


Yazımı burada sonlandırıyorum. Stream API hakkında örnekler üzerinden bilgi vermeye çalıştım. Bir sonraki yazıda Java 8 ile gelen tarih ve saat işlemlerinde kolaylık sağlayan LocalDate ve LocalTime sınıflarından, ayrıca Optinal sınıfından bahsedeceğim. Umarım yazdıklarım faydalı olmuştur. 👌


Kaynaklar

  1. https://kodedu.com/java-8-ebook/
  2. https://dogukanhan.com/java-stream-api-yazi-serisi/
  3. https://medium.com/@metinalniacik/allmatch-anymatch-ve-nonematch-kullan%C4%B1m%C4%B1-1449fbe96008
  4. https://medium.com/@sinanselimoglu/java-8-streams-paralel-i%CC%87%C5%9Flemler-3-a01233c8fd9f
  5. https://www.mobilhanem.com/stream-api-filter-sorted-map-match-reduce/
  6. https://howtodoinjava.com/java8/java-streams-by-examples/

Bir cevap yazın

E-posta hesabınız yayımlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir

Up Next:

Java 8 Yenilikleri - Bölüm 2

Java 8 Yenilikleri - Bölüm 2