Reactive Programming – 4 – Collections vs Streams
Seriyi takip edenlerin bildiği üzere Reactive Programming paradigmasına daha efektif giriş yapmak için bazı tanımalamalar / karşılaştırmalar yapmaktayız. Takip edemeyip etmek isteyenler buraya -> Reactive Programming bir göz atabilirler.
Hemen hemen bütün programlama dillerinde yapılan işlemler fonksiyon / metotların döndürdüğü değerlere (return) bağlı değil midir? İşte tam olarak bu döndürme işleminin yaşam döngüsüne bir bakıp çıkacağız.
Herhangi bir metot için Stream Return veya Collection Return yapılmasındaki farklar nelerdir? Metot / fonksiyon yaptığı işlemler sonucunda bir Stream döndürdüğünde neler oluyor? Collection döndürdüğünde neler oluyor?
Collections
Hepimizin bildiği gibi collection’lar belirli bir gruptaki verileri çeşitli algoritmalar altında tutmaya ya da gruplamaya ve bu verilerin üzerinde işlem/hesap yapmamıza olanak yarayan yapılardır. Yani collection’ları hem verileri tutmak/saklamak için hem de bu veriler üzerinde işlem yapmak için kullanıyoruz.
Streams
Stream’ler de ise durum biraz farklıdır. Verileri tutmaktan ziyade veriler üzerinde işlem yapmaya yoğunlaşır. Örneğin collections yapısında yaratılan veri bloğu üzerinde defalarca işlem yapılabilirken streams yapısında bu mümkün değildir. Yani streams altında bir veri bloğu oluşturulup işlendikten sonra aynı veri bloğunda tekrar işlem yapılamaz. Tekrar işlem yapmak için blok tekrar oluşturulmalı ve işleme alınmalıdır. Bu nokta “Collections vs Streams” karşılaştırmasındaki en ilgi çekici ve dikkat edilmesi gereken başlıktır.
Stream’lerin oluşturulması hakkında bilgi edinmek için -> Java 8 Stream API makalesine kısa bir göz atabilirsiniz.
Stream’ler aynı zamanda collection’lar üzerinde de işlem yapabilirler. Yani collection’ları bir veri bloğu olarak kullanabilirler. Bu durumda veri işlemede oldukça kullanışlı metotları bize sunarlar. (filter, map, count vb…) -> Java 8 Stream API Doc
Collections vs Streams
Belki de karşılaştırmadaki en önemli nokta bu yapıların veri ele alma şekilleri olarak karşımıza çıkmaktadır.
- Collection’lar veriyi gruplayan ve saklayan ve tekrar tekrar işlem yapılmasına izin veren yapılardı.Yani herhangi bir collection yaratıldığında bu collection için memory’ de yer ayrılır yani rezerve edilir.
- Stream’ler ise veriyi saklamayan işlemden sonra veriyi unutan yapılardı. Tam da burada şunu söyleyebiliriz. Stream’ler veriyi saklamadıklarından dolayı kaynak tüketiminde daha verimlidirler. Yani memory tüketimi/kullanımı açısından daha efektiftirler.
Dedik ya; herhangi bir metod için Stream Return veya Collection Return yapılmasının farkı nedir? Java NIO Path ile basit bir işlem yaptığımızı düşünelim. Bu işleme bağlı olarak yaratılan metotların bir veriyi return etmesini yani işlemesini kabaca inceleyelim.
1 2 3 4 5 6 |
static List<String> readAllLinesByCollection(Path path) static Stream<String> readAllLinesByStream(Path path) |
Böyle bir işlemde bir sonuç/return üretmek için
- readAllLinesByCollection metodu önce tüm satırları okur sonra bunları bir collection’a yazar ve sonuç döndürür.
- readAllLinesByStream metodu ise okuduğu satırları doğrudan döner. Bu yapıyı “Laziness-Seeking” davranış olarak adlandırabiliriz.
Evet, readAllLinesByStream ile bir veri tutma/storage edilmediğinden bir kazanç oluştu. Sizlere daha çok kazandıracak bir sır/örnek vereyim mi?
Bu dosyada herhangi bir arama/search işlemi yaptığımızı düşünelim. Ya da herhangi bir veri bloğunda bir arama/search yaptığımızı düşünelim. Bu durumda collections ve streams sonuç/return döndürmek için nasıl bir yol izleyecektir?
- readAllLinesByCollection tüm dosyayı okuyup bir collection içerisine koyacak ve arama işlemine başlayacaktır. Aranan kelime ya da değer belki de 3. satırdaydı. Ama metot tüm dosyayı memory’e kaydecek ve öyle işlem yapacak yani return/sonuç döndürecektir.
- readAllLinesByStream ise dosyayı okumaya başlayacak ve 3. satırda aradığı değeri bulduğu anda return/sonuç döndürecektir. -> anyMatch metot ‘una bir bakabilirsiniz.
Böyle bir senaryoda collection kullanmanın hatta bu senaryoyu büyük projelerde defalarca ve büyük veriler üzerinde kullanmanın getireceği maaliyetleri yani kaynak israfını düşününce stream alt yapısının önem derecesi ortaya çıkmaktadır.
Stream’lerin çalışma mantığına değindiğimize göre stream’ler…
- Storage: Stream verileri tutmaz/saklamaz. Sadece üzerlerinde işlem yapar.
- Collection‘lar veriyi saklar / depolar.
- Functinal Behaviour: Stream’ler data blokları üzerinde sadece işlem yapar ve buna bağlı olarak sonu dönerler. Bunu işlemleri de data blokları üzerinde bir değişiklik yapmadan gerçekleştirirler. Yani fonksiyonel programlama paradigmasına uygun hareket ederler. Örneğin collection gibi bir data bloğu üzerinde filtreleme yaparken filtrelenmiş datalardan yeni bir stream oluşturular. Yani ana data bloğu ve/veya kaynak collection üzerinde herhangi bir değişiklik yapmazlar.
- Collection‘lar data bloğu üzerinde işlem / değişiklik yaparlar.
- Laziness-Seeking: Stream işlemler intermediate ve terminal olmak üzere ikiye ayrılır. Intermediate işlemler lazy’dir. Stream’ler yukarıdaki readAllLinesByStream örneğinde olduğu gibi bir data bloğu üzerinde işlem yaparken sadece gerekli kısmı alıp yani gerektiği kadar kullanıp diğer dataları göz ardı edebilirler.
- Collection‘lar daima eager davranış sergilerler. Yani tüm daima tüm data’ya erişme eğilimindedirler.
- Infinite: Sonsuz sayıda element taşıyan data blokları üzerinde işlem yapabilirler.
- Collection‘lar bir data bloğunda önceden belirlenmiş miktarda element bulundururlar. Yani büyüklükleri önceden belirlenmiştir.
- Multiple Consumption: Stream’ler bir data bloğunda sadece bir kere işlem yapabilirler.
- Collection‘larda data blokları saklandığı ve/veya depolandığı için collection’lar data bloğunda tekrar tekrar işlem yapabilirler.
Stream’ler hangi koşullarda tercih edilmelidir?
- İşlem yapılacak data bloğu çok büyük ya da sonsuz ise
- Filtreleme işlemleri yapılacaksa
- Lazy davranışlar yapılan işlemleri daha verimli hale getirecekse stream güzel bir tercih olacaktır.
Limit, belirli, kalıcı ve tekrar tekrar kullanılması gereken bir data bloğu var ise Collection’lar tercih edilebilir.
Bu serinin ilk makalesinde ->Reactive Programlama Nedir? ifade ettiğimiz gibi reaktif programlamada stream’ler önemli bir yer kaplamaktadır. Bu makalede de veri yönetiminde sağladıkları avantajlar ile neden önemli olduğuna küçük de olsa bir ışık tutmaya çalıştık. Bir sonraki makalede de inşaallah Reactive Streams ve Asynchronous Streams konusunu ele almaya çalışacağız.