İçeriğe geç

Stream API ve Tembellik

Orta 10 dk Çok sık karşılaşılır

30 saniyede özet

Stream aşama aşama değil, eleman eleman çalışır: her eleman zincirin tamamından tek başına geçer. İşlemlerin sırasının önemi ve akışın erken durabilmesi buradan gelir.

Aynı işi iki farklı sırayla yapabilirsin: biri çabucak biter, öteki boşuna yorulur. Java’daki Stream’lerde de sıra, kimin ne kadar çalışacağını belirler.

  1. Bayt: Milyonlarca elemanlı bir Stream'den ilk üçünü istedim ve cevap anında geldi.

  2. Sen: Ama önce hepsinin filtrelenmesi gerekmiyor muydu?

  3. Bayt: Stream kuyruğa değil yolcuya bakar: her eleman bütün yolu tek başına yürür.

  4. Bayt: Üç yolcu kapıdan geçince iş biter. Kalanlar koltuğundan hiç kalkmaz!

Şu iki satır aynı sonucu verir, ama maliyetleri çok farklıdır:

Aynı sonuç, farklı maliyet
list.stream().filter(n -> n > 4).map(this::expensive).toList();
list.stream().map(this::expensive).filter(n -> n > 40).toList();

İkincisinde expensive her elemana uygulanır. Neden olduğunu anlamak için pipeline’ın nasıl aktığını bilmek gerekir.

Her eleman yolu tek başına yürür

Çoğumuz şöyle hayal ederiz: önce filter bütün listeyi süzer, sonra map kalanların hepsini dönüştürür. Bu yanlış.

Gerçekte her eleman zincirin tamamından tek başına geçer, sonra sıradaki elemana geçilir. Buna lazy evaluationİşi gerçekten ihtiyaç duyulana kadar ertelemek. Stream'de ara işlemler terminal işlem gelene kadar hiç çalışmaz.Sözlükte gör → deniyor:

5 → filter ✓ → map → 50 → collect
3 → filter ✕ (burada durdu, map hiç çağrılmadı)
8 → filter ✓ → map → 80 → collect
...

Bu tek gerçek, konunun geri kalanının hepsini açıklar.

Kafam karıştı, daha basit anlat

Önce herkes birinci kontrolden, sonra herkes ikinciden geçmez. Her yolcu bütün kontrollerden tek başına geçer, sonra sıradaki yolcu gelir.

Hızlı kontrolOrta

`list.stream().filter(p).map(f).toList()` çağrısında elemanlar hangi sırayla işlenir?

Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

Satır satır: eleman eleman akış

Dört elemanlı bir listede `filter(n > 4).map(n * 10)` çalıştırırsan, `map` lambda'sı kaç kez çağrılır? Cevabı göster

İki kez — yalnızca filtreyi geçen elemanlar için. Listedeki eleman sayısı kadar değil.

Kuyruk değil yolcu: her değer bütün yolu tek başına yürür.
Adım adım oku
  1. İlk değer 7, filter, map ve limit'ten tek başına geçip kapıya varır. Sıradaki değer bu sırada bekler.
  2. 2 filtreye takılır. map onu hiç görmez.
  3. 9 da bütün yolu yürür ve kapıya varır. limit(2) doldu, akış durur.
  4. 4 ve 8'e hiç dokunulmadı: ne filtrelendiler ne dönüştürüldüler.

Zincirden kim geçiyor?

ElemanElemanAkis.java
1List.of(5, 3, 8, 2).stream()
2 .filter(n -> n > 4)
3 .map(n -> n * 10)
şu an çalışan satır .toList();

Debug

Adım 1/9

stream Pipeline kuruldu ama hiçbir şey çalışmadı. Akışı başlatan şey bu satırdaki terminal işlem.

filter cagrisi
= 0
map cagrisi
= 0
Java 21UTF-8LF4:1

Sol/sağ ok tuşlarıyla da gezebilirsin.

Kafam karıştı, daha basit anlat

Filtreyi geçemeyen eleman orada durur, sonraki adımlara hiç uğramaz. Bu yüzden ucuz bir filtreyi pahalı bir işten önce koymak iş tasarrufudur.

Hızlı kontrolBaşlangıç

`list.stream().filter(x -> { System.out.println("bakıldı"); return true; });` satırı tek başına ne yazdırır?

Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

Bir Stream değişkenine atanıp iki kez terminal işlem uygulanırsa ne olur?

Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

Kendin izle

Simülatör her adımda bir elemanı bir aşama ilerletiyor. Dört pipeline’ı karşılaştır.

Stream pipeline — tembellik ve kısa devre

Tohum 4
numbers.stream()
    .filter(n -> n > 4)
    .map(n -> n * 10)
    .collect(toList());

Kaynak liste

53819274

Aşamalar · lambda çağrı sayısı

  • filter(n > 4)0 çağrı
  • map(n * 10)0 çağrı
  • collect(toList())0 çağrı

Sonuç

[]

Lambda çağrısı
0
Hiç bakılmayan
8
Kısa devre
hayır
Hız
Adım 0

Şu an ne oldu?

Pipeline bekliyor

Ara işlemler tanımlandı ama hiçbiri çalışmadı. Terminal işlem çağrılmadan bir Stream hiçbir şey yapmaz.

Aklında kalsın: Terminal işlem olmadan filter/map yazmak sessizce hiçbir şey yapmaz — derleyici de uyarmaz.

Olay günlüğü (0)

Henüz olay yok. Oynat veya adımla.

Sırayla dene:

  1. filter → map (varsayılan) — map yalnızca filtreyi geçen elemanlar için çağrılıyor. Aşama başına çağrı sayılarına bak.
  2. map → filter — aynı sonuç, ama map çağrı sayısı kaynak listenin tamamı kadar. Sırayı değiştirmek sonucu değil maliyeti değiştiriyor.
  3. findFirst — ilk sonuç bulunduğu anda duruyor. Hiç bakılmayan sayacı sıfırdan büyük: kaynaktaki kalan elemanlar okunmadı bile.
  4. sorted → limit — sorted tüm akışı tamponluyor. Tampon dolana kadar aşağıya tek bir eleman bile geçmiyor.
Hızlı kontrolOrta

Her Stream işlemini doğru kategoriye yerleştir.

Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

Sınıflandırılmamış

Ara işlem (tembel)

Stream döner, tek başına hiçbir şey çalıştırmaz

    Terminal işlem

    Pipeline'ı başlatır ve Stream'i tüketir

      Tembelliğin sınırı

      Ara işlem, terminal işlem.

      ÖrneklerNe zaman çalışır
      Ara (tembel)filter, map, peek, flatMapTerminal işlem gelene kadar hiç
      Terminalcollect, forEach, reduce, findFirst, anyMatchPipeline’ı başlatan şey

      Bir terminal operationStream'i tüketip sonuç üreten işlem — `collect`, `forEach`, `count`. Pipeline ancak bu çağrıldığında çalışmaya başlar.Sözlükte gör → yoksa hiçbir şey çalışmaz — ve derleyici bunu söylemez:

      Sessizce hiçbir şey yapmayan kod
      // Bu satır tek bir lambda bile çalıştırmaz
      users.stream().filter(User::isActive).map(User::email);
      peek neden güvenilmez?· istersen atla

      peek hata ayıklamada yanıltıcıdır: terminal işlem yoksa hiç çalışmaz, kısa devre varsa yalnızca bazı elemanlar için çalışır.

      short-circuitCevap belli olur olmaz durmak. `findFirst` ve `anyMatch` tüm veriyi gezmeden çıkar — sonsuz stream'leri çalışabilir kılan şey budur.Sözlükte gör →. findFirst, anyMatch, allMatch, noneMatch ve limit sonuç kesinleştiği anda pipeline’ı durdurur.

      Bunun en çarpıcı sonucu: sonsuz bir Stream’i sonlandırabilirsin.

      Sonsuz kaynak, sonlu sonuç
      Stream.iterate(1, n -> n + 1) // sonsuz
      .filter(n -> n % 7 == 0)
      .limit(3) // kısa devre — üç eleman bulunca durur
      .toList(); // [7, 14, 21]

      limit olmasaydı bu program asla bitmezdi.

      Durumlu ara işlemler. sorted() ve distinct() durumludur: ilk sonucu üretebilmek için akışın tamamını görmek zorundadırlar.

      Bu program asla bitmez
      Stream.iterate(1, n -> n + 1)
      .sorted() // sonsuz akışı sıralamaya çalışıyor — buraya kilitlenir
      .limit(3)
      .toList();
      Kafam karıştı, daha basit anlat

      Sonunda bir sonuç isteyen adım yoksa Stream hiç çalışmaz. Sonuç kesinleştiği anda da durur, bu yüzden sonsuz bir akışı bile güvenle bitirebilirsin.

      Hızlı kontrolOrta

      `Stream.iterate(1, n -> n + 1).sorted().limit(3).toList()` çalıştırılırsa ne olur?

      Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

      Tuzaklar

      İşlem sırası bir performans kararıdır. Kural basit: ucuz ve eleyici işlemleri öne al.

      Sıra önemlidir
      users.stream()
      .map(this::loadFullProfile) // her kullanıcı için DB sorgusu
      .filter(p -> p.isActive()) // sonra çoğunu at
      .toList();
      users.stream()
      .filter(User::isActive) // önce ele — bedava
      .map(this::loadFullProfile) // yalnızca kalanlar için DB sorgusu
      .toList();

      Sonuç ikisinde de aynı. Fark, loadFullProfile’ın kaç kez çağrıldığı.

      Stream tek kullanımlıktır.

      IllegalStateException
      var stream = list.stream();
      stream.filter(...).toList();
      stream.map(...).toList(); // IllegalStateException: stream has already been operated upon
      parallelStream sihirli değil· istersen atla

      Küçük listelerde ya da ağ ve disk bekleyen lambda’larda parallelStream() genellikle yavaşlatır. Varsayılan olarak bütün uygulamanın paylaştığı ortak ForkJoinPool’u kullanır; orada bekleyen bir çağrı başka yerleri de etkiler. Ölçmeden kullanılmaz.

      Aşağıdaki örnek bir bankanın kart harcamaları üzerinde: işlem sırasını log’layan bir pipeline, ilk şüpheli harcamada duran kısa devre, taksit tarihlerini üreten sonsuz akış ve ekstredeki kategori özeti.

      Derinleş · Kart harcamalarında stream tembelliği: görünür ve işe yarar 5 dosya · ~78 satır · ilk okumada atlayabilirsin
      Proje dosyaları

      src/main/java/statement/ CardTransaction.java Veri: bir kart harcaması, basit bir record.

      src/main/java/statement/CardTransaction.java
      public record CardTransaction(long id, String category, BigDecimal amount, TxStatus status) {
      // category comes from the merchant category code: "Market", "Akaryakıt", "Restoran", …
      }

      src/main/java/statement/ LazinessDemo.java Sıra: her harcama bütün aşamalardan tek tek geçer; findFirst ilk şüpheli harcamayı bulunca durur.

      src/main/java/statement/LazinessDemo.java
      public class LazinessDemo {
      public static void main(String[] args) {
      // The fraud rule wants the FIRST transaction above the review threshold.
      Optional<CardTransaction> firstSuspicious = transactions().stream()
      .peek(t -> System.out.println("filter " + t.id()))
      .filter(t -> t.amount().compareTo(new BigDecimal("5000")) > 0)
      .peek(t -> System.out.println("map " + t.id()))
      .findFirst();
      // filter 1
      // filter 2
      // map 2 <- transaction 2 is the first match: 3..1000 are never touched
      System.out.println(firstSuspicious.map(CardTransaction::id).orElseThrow()); // 2
      // Nothing at all is printed here: no terminal operation, so nothing runs.
      transactions().stream().peek(t -> System.out.println("never " + t.id())).filter(t -> true);
      }
      static List<CardTransaction> transactions() {
      return IntStream.rangeClosed(1, 1000)
      .mapToObj(i -> new CardTransaction(i, i % 2 == 0 ? "Market" : "Akaryakıt",
      BigDecimal.valueOf(i == 2 ? 9_000 : 250), TxStatus.POSTED))
      .toList();
      }
      }

      src/main/java/statement/ InstallmentDates.java Sonsuz kaynak ve limit: taksit tarihleri sonsuz bir akıştan alınıyor; tembellik olmadan bu satır bitmezdi.

      src/main/java/statement/InstallmentDates.java
      public class InstallmentDates {
      // A purchase in 6 installments: the next six monthly due dates.
      // An infinite source is fine: limit() is short-circuiting, so only 6 are ever generated.
      public static List<LocalDate> schedule(LocalDate firstDue, int installments) {
      return Stream.iterate(firstDue, due -> due.plusMonths(1))
      .map(InstallmentDates::nextBusinessDay) // a due date never falls on a weekend
      .limit(installments)
      .toList();
      }
      private static LocalDate nextBusinessDay(LocalDate day) {
      return switch (day.getDayOfWeek()) {
      case SATURDAY -> day.plusDays(2);
      case SUNDAY -> day.plusDays(1);
      default -> day;
      };
      }
      }

      src/main/java/statement/ CategoryReport.java Gerçek iş: ekstredeki kategoriye göre harcama özeti; gruplama, toplama ve sıralama tek pipeline'da.

      src/main/java/statement/CategoryReport.java
      public class CategoryReport {
      // The statement's "where did your money go" box: posted spending per category,
      // highest first. Refunds and reversals are excluded. One pass, no intermediate lists.
      public static List<Map.Entry<String, BigDecimal>> spendingByCategory(List<CardTransaction> txs) {
      Map<String, BigDecimal> byCategory = txs.stream()
      .filter(t -> t.status() == TxStatus.POSTED)
      .collect(Collectors.groupingBy(CardTransaction::category,
      Collectors.reducing(BigDecimal.ZERO, CardTransaction::amount, BigDecimal::add)));
      return byCategory.entrySet().stream()
      .sorted(Map.Entry.<String, BigDecimal>comparingByValue().reversed())
      .toList();
      }
      }

      src/main/java/statement/ Pitfalls.java Tuzaklar: tekrar kullanılan stream ve yan etkili peek.

      src/main/java/statement/Pitfalls.java
      public class Pitfalls {
      static void reuse(List<CardTransaction> txs) {
      Stream<CardTransaction> posted = txs.stream().filter(t -> t.status() == TxStatus.POSTED);
      long count = posted.count();
      // posted.toList(); IllegalStateException: stream has already been operated upon or closed
      // A stream is a one-shot pipeline. Keep a Supplier<Stream<CardTransaction>> for two passes.
      }
      static long countWithPeek(List<CardTransaction> txs, List<Long> seen) {
      // Since Java 9, count() may compute the size of a sized source (a List, no filter)
      // without running the pipeline: this peek never runs and `seen` stays empty.
      // Never rely on peek for work; collect what you need instead.
      return txs.stream().peek(t -> seen.add(t.id())).count();
      }
      }

      Kendini sına

      Önce hızlı bir ısınma: puan yok, kayıt yok.

      Şimşek turu1/5

      filter ve map, sonda toList gibi bir toplayıcı işlem çağrılmadan hiçbir şey yapmaz.

      Soru 1/4Orta

      Bu program ne yazdırır?

      Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.
      LazyStream.java
      1var list = List.of(1, 2, 3);
      2
      3list.stream()
      4 .filter(n -> {
      5 System.out.println("filter: " + n);
      6 return n > 1;
      7 })
      8 .map(n -> {
      9 System.out.println("map: " + n);
      10 return n * 10;
      11 });
      12
      13System.out.println("bitti");
      Java 21UTF-8LF

      Aklında kalacak üç şey

      1. 1 Her eleman zincirin tamamından tek başına geçer. 'Önce hepsi filtrelenir, sonra hepsi dönüştürülür' yanlıştır.
      2. 2 Ara işlemler tembeldir: sonda bir toplayıcı işlem çağrılmazsa filter ve map hiçbir şey yapmaz, derleyici de uyarmaz.
      3. 3 sorted ve distinct her şeyi görmeden sonuç veremez. Bu yüzden tembelliği bozarlar ve sonsuz bir akışta hiç bitmezler.
      Sonraki kapı Uzun bir if-else zinciri, tek bir switch ifadesine nasıl dönüşür? Java 8'den 21'e — Her Özellik Bir Hata Sınıfı · 10 dk

      5 kart sonraki derste seni bekliyor

      0/5 kart bu dersten toplandı