• Anasayfa
  • Eğitimler
    • JavaScript Eğitimi
    • Angular 2 Eğitimi
    • React.js Eğitimi
    • Java 8 Eğitimi
    • Java EE 7 Eğitimi
    • Spring Framework Eğitimi
    • Git Eğitimi
  • Online Eğitimler
    • Online React.js Eğitimi
    • Online Angular 2 Eğitimi
    • Online Spring Boot Eğitimi
  • Referanslar
  • Hakkında
  • İletişim
KodEdu
  • Anasayfa
  • Eğitimler
    • JavaScript Eğitimi
    • Angular 2 Eğitimi
    • React.js Eğitimi
    • Java 8 Eğitimi
    • Java EE 7 Eğitimi
    • Spring Framework Eğitimi
    • Git Eğitimi
  • Online Eğitimler
    • Online React.js Eğitimi
    • Online Angular 2 Eğitimi
    • Online Spring Boot Eğitimi
  • Referanslar
  • Hakkında
  • İletişim

Java 8 – Stream API

  • Posted by Kodedu
  • Categories backend, Genel, Uncategorized, Yazılar
  • Date 9 Ekim 2014

Java 8 içerisinde yığınsal verileri kolay işlemek açısından Stream API yeniliği getirilmiştir.

Stream API yığınsal veriler üzerinde çalışmaktadır. Yığınsal veri deyince ilk akla gelen hiç şüphesiz diziler (byte[],String[] gibi ) ve Java Collection API bileşenleridir (List,Set gibi)

Stream API, bu gibi yığınsal veriler üzerinde çeşitli sık kullanılan operasyonları kolay, özlü ve verimli bir biçimde koşturmaya olanak tanımaktadır. Bu operasyonlardan en sık kullanılanları aşağıdaki gibidir.

Metod Açıklama

filter

Filtreleme

forEach

iterasyon

map

Dönüştürme

reduce

İndirgeme

distinct

Tekilleştirme

sorted

Sıralama

limit

Aralık alma

collect

Türe dönüşüm

count

Sayma

…​

Bu operasyonlar ve daha fazlası java.util.stream.Stream arayüzü içinde bulunmaktadır. Stream arayüzünün sadeleştirilmiş hali aşağıdaki gibidir.

public interface Stream<T> extends BaseStream<T, Stream<T>> {

    Stream filter(Predicate<? super T> predicate);
     Stream map(Function<? super T, ? extends R> mapper);
    IntStream mapToInt(ToIntFunction<? super T> mapper);
    LongStream mapToLong(ToLongFunction<? super T> mapper);
    DoubleStream mapToDouble(ToDoubleFunction<? super T> mapper);
     Stream flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);
    IntStream flatMapToInt(Function<? super T, ? extends IntStream> mapper);
    LongStream flatMapToLong(Function<? super T, ? extends LongStream> mapper);
    DoubleStream flatMapToDouble(Function<? super T, ? extends DoubleStream> mapper);
    Stream distinct();
    Stream sorted();
    Stream sorted(Comparator<? super T> comparator);
    Stream peek(Consumer<? super T> action);
    Stream limit(long maxSize);
    Stream skip(long n);
    void forEach(Consumer<? super T> action);
    void forEachOrdered(Consumer<? super T> action);
    Object[] toArray();
     A[] toArray(IntFunction<A[]> generator);
    T reduce(T identity, BinaryOperator accumulator);
    Optional reduce(BinaryOperator accumulator);
     U reduce(U identity,
                 BiFunction<U, ? super T, U> accumulator,
                 BinaryOperator combiner);
     R collect(Supplier supplier,
                  BiConsumer<R, ? super T> accumulator,
                  BiConsumer<R, R> combiner);
    <R, A> R collect(Collector<? super T, A, R> collector);
    Optional min(Comparator<? super T> comparator);
    Optional max(Comparator<? super T> comparator);
    long count();
    boolean anyMatch(Predicate<? super T> predicate);
    boolean allMatch(Predicate<? super T> predicate);
    boolean noneMatch(Predicate<? super T> predicate);
    Optional findFirst();
    Optional findAny();
}

Stream nesnesi nasıl elde edilir?

Stream türünden nesneler çeşitli yollarla elde edilebilmektedir.

Collection API ile

Collection arayüzü türünden türeyen tüm nesneler, stream() veya parallelStream() metodlarını çağırarak Stream türünden bir nesne elde edebilmektedir.

public interface Collection<E> extends Iterable<E> {

...

    default Stream stream() {
        return StreamSupport.stream(spliterator(), false);
    }

    default Stream parallelStream() {
        return StreamSupport.stream(spliterator(), true);
    }

...

}

stream() metodu ile elde edilen Stream nesnesi yapacağı işlemleri ardışıl olarak yaparken, parallelStream() metoduyla elde edilen Stream nesnesi, bazı operasyonları paralel olarak koşturabilmektedir.

Örneğin;
List names = Arrays.asList("Ali","Veli","Selami"); (1)

Stream stream = names.stream(); (2)
Stream parallelStream = names.parallelStream(); (2)
  1. Collection türünden bir nesne
  2. Ardışık stream
  3. Paralel stream

New I/O ile

Java içerisindeki bazı I/O sınıfları üzerinden Stream nesneleri elde edilebilmektedir.

Path dir = Paths.get("/var/log"); (1)

Stream pathStream = Files.list(dir); (2)
  1. /var/log dizinine denk gelen bir Path nesnesi
  2. Files#list metodu üzerinden bir Stream nesnesi

IntStream, DoubleStream, LongStream ile

Stream arayüzü BaseStream arayüzünden türemektedir. Stream arayüzüne benzer biçimde IntStream, DoubleStream ve LongStream arayüzleri de BaseStream arayüzünden türemektedir.

Stream arayüzü türünden nesneler tüm veri tipleriyle çalışmak için oluşturulan bir arayüzken, buradaki üç eleman ise, sadece sınıf başındaki tip ile özel olarak çalışmak için oluşturulan arayüzlerdir.

Örneğin;
IntStream intOf = IntStream.of(1, 2, 3); (1)
IntStream intRange = IntStream.range(1, 10); (2)

DoubleStream doubleOf = DoubleStream.of(1.0, 3.5, 6.6); (3)

LongStream longOf = LongStream.of(3, 5, Long.MAX_VALUE,9); (4)
LongStream longRange = LongStream.range(1, 100); (5)
  1. (1,2,3) içeren IntStream nesnesi
  2. (1,…​,10) arasını içeren IntStream nesnesi
  3. (1.0, 3.5, 6.6) içeren DoubleStream nesnesi
  4. (3, 5, Long.MAX_VALUE,9) içeren LongStream nesnesi
  5. (1,…​,100) arasını içeren LongStream nesnesi

Stream API Örnekleri

Bu kısımda çeşitli Stream API metodları ile küçük uygulamalar yer almaktadır.

forEach

Stream içerisindeki yığınsal veriyi tek tek tüketmek için yapılandırılmıştır. Consumer arayüzü türünden bir parametre bekler.

List names = Arrays.asList("Ali","Veli","Selami","Cem","Zeynel","Can","Hüseyin");

Stream stream = names.stream();

stream.forEach(name -> {
    System.out.println(name);
});

// veya stream.forEach(System.out::println);

filter

Stream içerisindeki yığınsal veri üzerinde süzme işlemi yapar. Predicate arayüzü türünden bir parametre ile filtreleme işlemini yapar.

List names = Arrays.asList("Ali", "Veli", "Selami", "Cem", "Zeynel", "Can", "Hüseyin");

Stream stream = names.stream(); (1)

Predicate predicate = name -> name.length() < 4; (2)

Stream filtered = stream.filter(predicate); (3)

filtered.forEach(System.out::println); (4)
  1. Stream nesnesi elde ediliyor.
  2. Predicate sorgusu hazırlanıyor
  3. Süzme işlemi yapılıyor, yeni bir Stream nesnesi sunuluyor.
  4. Listeleniyor. [Ali, Cem, Can]
Note
Stream nesneleri tek kullanımlıktır. Stream nesnesinin çoğu metodu yeni bir Stream nesnesi sunmaktadır. Bu sebeple tüm operasyonlar zincirlemeli olarak yapılabilmektedir.
Örneğin;
names
    .stream()
    .filter(name -> name.length() == 4)
    .forEach(System.out::println);

distinct

Bir Stream içerisinden tekrarlı veriler çıkarılmak isteniyorsa distinct metodundan faydalanılabilir.

IntStream stream = IntStream.of(1, 1, 2, 3, 5, 8, 13, 13, 8); (1)

stream
    .distinct()
    .forEach(System.out::println); (2)
  1. IntStream nesnesi
  2. [1,2,3,5,8,13]

sorted

Stream içerisindeki yığınsal verinin sıralanmış Stream nesnesini döndürür.

IntStream stream = IntStream.of(13, 1, 3, 5, 8, 1, 13, 2, 8); (1)

stream
        .sorted()
        .forEach(System.out::println); (2)
  1. IntStream nesnesi
  2. [1,1,2,3,5,8,8,13,13]

limit

Bir Stream yığını içerisindeki ilk N veri barındıran yeni bir Stream nesnesi sunmaktadır.

LongStream range = LongStream.range(1, 10000); (1)

        range
                .limit(10)
                .forEach(System.out::println); (2)
  1. (1,…​,10000) arasını içeren bir Stream
  2. İlk 10 veri : [1,…​,10]

count

Stream içerisindeki eleman sayısını hesaplar.

IntStream range = IntStream.range(1, 10);
IntStream rangeClosed = IntStream.rangeClosed(1, 10);

System.out.println(range.count()); (1)
System.out.println(rangeClosed.count()); (2)
  1. 9
  2. 10

collect

Stream türünden nesneler, yığın verileri temsil eden özel nesnelerdir. Fakat Stream biçimi bir veri yapısı sunmamaktadır. collect metodu ağırlıklı olarak , Stream nesnelerini başka biçimdeki bir nesneye, veri yapısına dönüştürmek için kullanılmaktadır.

Stream#collect metodu Collector türünden bir parametre kabul etmektedir. Bu parametre ile istendik türe dönüşüm sağlanmaktadır. Collector türünden arayüzler, Collectors sınıfının çeşitli statik metodlarıyla elde edilebilmektedir.

List names = Arrays.asList("Ali", "Veli", "Selami", "Veli", "Selami", "Can", "Hüseyin");

List list = names.stream().collect(Collectors.toList()); (1)

Set set = names.stream().collect(Collectors.toSet()); (2)

Long count = names.stream().collect(Collectors.counting()); (3)

String collect = names.stream().collect(Collectors.joining(" - ")); (4)

Map<Integer, List> integerListMap = names.stream().collect(Collectors.groupingBy(name -> name.length())); (5)
  1. Stream nesnesinden List nesnesi üretir.List[“Ali”, “Veli”, “Selami”, “Veli”, “Selami”, “Can”, “Hüseyin”]
  2. Stream nesnesinden Set nesnesi üretir.Set[“Ali”, “Veli”, “Selami”,”Can”, “Hüseyin”]
  3. Stream nesnesinin eleman sayısını üretir.7
  4. Stream nesnesini birleştirir.Ali – Veli – Selami – Veli – Selami – Can – Hüseyin
  5. Stream nesnesini isim uzunluğuna göre gruplar.
Table 1. Map<Integer, List> nesnesinin temsili tablo görünümü
Key Value

3

Ali

Can

4

Veli

Veli

6

Selami

Selami

7

Hüseyin

map

Stream içindeki yığınsal olarak bulunan her bir veriyi dönüştürmeye olanak tanır. Dönüştürüm işlemi Stream içerisindeki her bir öğe için ayrı ayrı yapılmaktadır. Stream#map metodu Function türünden bir parametre beklemektedir.

Örnek 1;

Bir List içindeki her bir öğenin harflerini büyütelim.

List names = Arrays.asList("Ali", "Veli", "Selami", "Cem");

Stream stream = names.stream(); (1)
Stream upperStream= stream.map(name -> name.toUpperCase()); (2)
List upperNames = upperStream.collect(Collectors.toList()); (3)
  1. Stream nesnesi elde ediliyor
  2. Her bir ismin harfleri büyütülüyor
  3. List[“ALİ”,”VELİ”,”SELAMİ”,”CEM”]
Örnek 2;

1,5 arası sayıların karelerini hesaplayalım.

IntStream
        .rangeClosed(1, 5)
        .map(n -> n*n)
        .forEach(System.out::println); (1)
  1. [1, 4, 9, 16, 25]

reduce

Bir Stream içerisindeki verilerin teker teker işlenmesidir. Teker teker işleme sürecinde, bir önceki adımda elde edilen sonuç bir sonraki adıma girdi olarak sunulmaktadır. Bu sayede yığılmlı bir hesaplama süreci elde edilmiş olmaktadır.

Stream#reduce metodu ilk parametrede identity değeri, ikinci parametrede ise BinaryOperator türünden bir nesne kabul etmektedir.

reduce işleminde bir önceki hesaplanmış değer ile sıradaki değer bir işleme tabi tutulmaktadır. İşleme başlarken bir önceki değer olmadığı için bu değer identity parametresinde tanımlanmaktadır.

Örnek 1;

1,2,3,4,5 sayılarının toplamını hesaplayalım.

int result = IntStream
                .of(1, 2, 3, 4, 5)
                .reduce(0, (once, sonra) -> {
                    System.out.format("%d - %d %n", once, sonra);
                    return once + sonra;
                });

Toplama işleminde 0 etkisiz eleman olduğu için, identity değeri 0 seçildi.

Uygulama çalıştırıldığında 15 sonucu elde edilir. reduce içindeki Lambda ifadesinde ise aşağıdaki çıktı elde edilir.

 0 - 1
 1 - 2
 3 - 3
 6 - 4
10 - 5

Önce hesaplanmış değeri, Sonra ise sıradaki değeri temsil etmektedir. Bir adımda çıkan hesaplamanın sonucu, bir sonraki adımda (satırda) Önce sütununa sunulmaktadır.

Önce Sonra Hesaplama

0

1

0+1 ↵

1

2

1+2 ↵

3

3

3+3 ↵

6

4

6+4 ↵

10

5

10+5 = 15

Örnek 2;

1,2,3,4,5 sayılarının çarpımını hesaplayalım.

// Lambda ile
int result = IntStream
                .of(1, 2, 3, 4, 5)
                .reduce(1, (once, sonra) -> once*sonra);

// veya Method reference ile
result = IntStream
            .of(1, 2, 3, 4, 5)
            .reduce(1, Math::multiplyExact);

map & reduce

map ve reduce işlemleri birlikte kullanımı çok fazla tercih edilen iki operasyondur. Bu operasyonları önemli kılan ise, bu iki operasyonun dağıtık sistemler için çok uygun olmasıdır. Piyasada Map & Reduce işlemlerini dağıtık mimarilerde kullanan birçok teknoloji bulunmaktadır. Tabiki Java 8 ile kullandığımız map & reduce ikilisi tek JVM üzerinde koştuğu için dağıtık değildir.

Örneğin;

  • Hazelcast
  • Hadoop
  • MongoDB gibi.
Örnek 1;

Elimizde Person sınıfı türünden 5 nesne bulunsun. Bu 5 nesne içinden tüm kişilerin yaşlarının ortalamasını hesaplamak isteyelim. Böyle bir senaryo için map & reduce metodlarını birlikte tercih edebiliriz.

public class Person {
    private String name;
    private Integer age;

   // getter, setter ve constructor metodları
}
Person p1 = new Person("Ahmet", 12);
Person p2 = new Person("Ali", 20);
Person p3 = new Person("Ayşe", 30);
Person p4 = new Person("Murat", 51);
Person p5 = new Person("Zeynep", 60);

List personList = Arrays.asList(p1, p2, p3, p4, p5); (1)

personList
        .stream() (2)
        .map(p -> p.getAge()) (3)
        .map(Double::valueOf) (4)
        .reduce(0, (a, b) -> (a + b)/2); (5)
  1. Person listesi
  2. Stream nesnesi elde ediliyor
  3. Nesnenin yaş alanına göre mapping yapılıyor.
  4. Integer → Double dönüşümü
  5. Ortalamalar hesaplanıyor
Örnek 2;

Person listesinde bazı kişilerin yaş alanları null değer içersin. Bu durumda çalışma zamanında nullpointerexception istisnası elde edilecektir. Bu gibi bir durumda filtreleme yapısını işlemimize ekleyebiliriz.

Person p1 = new Person("Ahmet", 12);
Person p2 = new Person("Ali", null);
Person p3 = new Person("Ayşe", 30);
Person p4 = new Person("Murat", null);
Person p5 = new Person("Zeynep", 60);

List personList = Arrays.asList(p1, p2, p3, p4, p5);

personList
        .stream()
        .filter(Objects::nonNull) // Dikkat !!
        .map(p -> p.getAge())
        .map(Double::valueOf)
        .reduce(0, (a, b) -> (a + b)/2);

Parallel Stream

Stream arayüzü içindeki metodlardan ardışık işletilmesi gerekmeyenler, istenirse, CPU üzerinde paralel olarak koşturulabilmektedir. Bu sayede CPU çekirdeklerini tam verimli olarak kullanmak mümkün olmaktadır.

Stream API içerisinde paralel Stream elde etmek oldukça kolaydır.

Örneğin

List ints = Arrays.asList(1, 3, 5, 7, 9, 11, 13, 15);

Stream stream = ints.stream();
Stream parallelStream = ints.parallelStream();

Collection#stream() metoduyla ardışıl (sequential) , Collection#parallelStream() metoduyla da paralel Stream nesnesi elde edilmektedir. Elde edilen paralel Stream nesnesiyle koşturulan işlemler paralel olarak koşabilmektedir.

Aynı zamanda bir ardışıl Stream nesnesinden paralel Stream nesnesi elde edilebilmektedir. Bunun için Stream#parallel metodu kullanılmaktadır.

Örneğin;
List ints = Arrays.asList(1, 3, 5, 7, 9, 11, 13, 15);

Stream stream = ints.stream(); // Ardışıl
Stream parallelStream = stream.parallel(); // Paralel

Aynı zamanda bir paralel Stream nesnesinden ardışıl Stream nesnesi de elde edilebilmektedir. Bunun için Stream#sequential metodu kullanılmaktadır.

Örneğin;
List ints = Arrays.asList(1, 3, 5, 7, 9, 11, 13, 15);

Stream parallelStream = ints.parallelStream(); // Paralel
Stream stream = stream.sequential(); // Ardışıl
Örnek

Aşağıda bir dizi sayısal ifadeyi filtreleyen, sıralayan ve çıktılayan bir kod parçası görmekteyiz. Ayrıca bu işlemlerin paralel Stream nesnesiyle yapılmak istendiğini görüyoruz.

List ints = Arrays.asList(1, 5, 3, 7, 11, 9, 15, 13);

 ints
    .parallelStream() // Paralel Stream
    .filter(Objects::nonNull) // null değilse
    .filter(n -> n > 0) // pozitif sayı ise
    .sorted() // sırala
    .forEach(System.out::println); // çıktıla

Bu örnekte filter ve sorted paralel olarak koşturulabilirdir. Fakat forEach metodu doğası gereği öğeleri ardışık çıktılamalıdır. İşte tam da bu adımda elimizdeki paralel Stream nesnesi ardışıl Stream nesnesine dönüştürülmektedir ve ardından forEach işlemini koşturmaktadır.

Yani elimizde paralel Stream nesnesi varsa, bu zincirlemeli işlemin her adımında paralel koşturma yapılacağı anlamını taşımamaktadır.

Lazy & Eager operasyonlar

Literatürde Lazy bir işlemin geç, ötelenmiş olarak yapılması iken, Eager ise yapılacak işlemin emir verilir verilmez yapılmasını temsilen kullanılır.

Stream API içerisindeki bazı operasyonlar Lazy bazıları ise Eager olarak koşturulmaktadır. Lazy davranışlı olan zincirli görevler, bir Eager operasyona gelene kadar koşturulmamaktadır.

List names = Arrays.asList(1,2,3,6,7,8,9);

Stream stream = names
    .stream()
    .filter(Objects::nonNull)
    .filter(n->n%2==1)
    .map(n->n*2);

Örneğin yukarıdaki liste üzerinde yapılmak istenen 2 filter ve 1 map işlemi Lazy işlemlerdir. Kod parçası bu haliyle çalıştırıldığında ne bir filtreleme ne de bir dönüştürme işlemi yapılacaktır. Burada yapılan sadece Stream nesnesini hazırlamaktır. Lazy işlemler gerekmedikçe işleme konulmamaktadır.

List names = Arrays.asList(1,2,3,6,7,8,9);

Stream stream = names
    .stream()
    .filter(Objects::nonNull) (1)
    .filter(n->n%2==1) (2)
    .map(n->n*2) (3)

stream.forEach(System.out::println); // Dikkat !! (4)
  1. Lazy
  2. Lazy
  3. Lazy
  4. Eager

Fakat bu hazırlanan Stream nesnesi, yukarıdaki gibi bir Eager operasyonla karşılaşırsa, önceki zincirlerde biriken Lazy işlemleri de harekete geçirecektir. Yani (4) numaradaki işlem, (1)(2)(3) numaralı işlemlerin tetikleyicisi konumundadır.

Tag:backend

  • Share:
author avatar
Kodedu

Previous post

Writing Books with Asciidoc FX
9 Ekim 2014

Next post

Java 8 ve JVM Dilleri
10 Ekim 2014

You may also like

api-logo
Swagger Nedir? Neden kullanılır?
10 Ekim, 2018
spring-cli-logo
Spring CLI ile Spring Boot Projeleri Hazırlamak
21 Ağustos, 2017
eureka_architecture
Spring Cloud Netflix ve Eureka Service Discovery
3 Temmuz, 2017

    5 Comments

  1. Halil ibrahim küley
    9 Mayıs 2019
    Cevapla

    Hocam konuyu çok güzel açıklamışsınız, çok faydalı oldu, ellerinize sağlık, çok teşekkür ederim.

  2. Ömer Faruk Genç
    17 Temmuz 2019
    Cevapla

    Yazdıklarınız yıllar sonra bile işe yarar ve kullanışlı oluyor. Emeğiniz için çok teşekkür ederim.

    • Rahman Usta
      3 Ekim 2019
      Cevapla

      Teşekkür ederim.

  3. Muhammed
    9 Ekim 2019
    Cevapla

    Teşekkür ederim Çok detaylı ve güzel anlatmışıınız.

Leave A Reply Cevabı iptal et

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

E-posta listesine kayıt olun!






Gözde yazılar

Java Mimarisiyle Kurumsal Çözümler : Kurumsal Java Kitabı
16Eyl2012
Java API for JSON Processing – Stream bazlı JSON Üretmek ve Tüketmek
06Ağu2013
AsciidocFX projesi Duke’s Choice Award 2015 ödülünü kazandı
28Eki2015
Knockout.js – Hesap Makinesi Örneği
02Şub2013

Son Yazılar

  • Java’da Record’lar 27 Ocak 2020
  • Swagger Nedir? Neden kullanılır? 10 Ekim 2018
  • Spring CLI ile Spring Boot Projeleri Hazırlamak 21 Ağustos 2017
  • Spring Cloud Netflix ve Eureka Service Discovery 3 Temmuz 2017
  • Online React.js Eğitimi ardından (15-25 Mayıs 2017) 31 Mayıs 2017

Son Yorumlar

  • Naïve Bayes Sınıflandırma Algoritması için Rahman Usta
  • Naïve Bayes Sınıflandırma Algoritması için Mete
  • YAML Nedir? Neden YAML Kullanmalıyız? için kara
  • JWT (JSON Web Tokens) Nedir? Ne işe yarar? için Furkan
  • YAML Nedir? Neden YAML Kullanmalıyız? için shahriyar

Get Java Software

Arşivler

Bizi takip edin

React.js Eğitimi Başlıyor
11-22 Eylül, 2017
Eğitmen
Rahman Usta
İletişim

merhaba@kodedu.com

  • Hakkında
  • Gizlilik Politikası
  • İletişim
  • Referanslar
Kodedu Bilişim Danışmanlık
Cemil Meriç mah. Çelebi sok.
No:16/3 Ümraniye/İSTANBUL
Tel: 0850 885 38 65
Alemdağ V.D.: 8960484815

KODEDU © Tüm hakları saklıdır.