---
title: "Akış API'si"
source: 'https://academia.sh/tr/kurslar/java-standart-kutuphane/akis-apisi'
course: 'Standart Kütüphane ve Akışlar'
language: tr
updated: '2026-08-17T18:09:43+00:00'
license: 'CC BY-SA 4.0'
---

# Akış API'si

Java'nın veri akışı zinciri on işlemden üçünü kaynaktan bağımsız garanti eder, beşini kaynağın sırasına devreder, ikisini çağırana bırakır. Ara işlem sonlandırıcı olmadan hiç çalışmaz, ama sonlandırıcı çağrıldığında bile hangi öğenin ziyaret edileceği garanti değildir. Aynı veri akışını iki kez tüketmek ve gezerken kaynağı büyütmek ikisi de istisnayla düşer.

Önceki ders tek bir lambda ifadesinin çevresinden ne yakaladığını ölçtü ve dört sözdizim
biçiminin aynı hedefe bağlandığını gösterdi. Standart kütüphanenin asıl kullanım biçimi
böyle tek bir lambda değil, birbirine art arda bağlanmış birkaç lambdadır — bir **veri
akışı** zinciri. Her halka kendi başına doğru çalışsa da, zincire dizildiğinde ayrı bir soru
doğar: zincirin hangi parçası kaynağın sırasına bağlıdır, hangisi her koşulda aynı sırayı
verir, hangisi bir sonlandırıcı olmadan hiç çalışmaz, ve hangi kural bütünüyle çağırana
kalır?

Bu ders on ayrı iddiayı koşturarak sınıflar: her biri için gözlemlenen davranış kaynaktan
bağımsız mı (arayüz garantisi), kaynağın kendi sırasına mı bağlı, yoksa çağıranın uyduğu
bir kural mı? Ölçüm biçimi öncekilerle aynıdır — kod yazılır, çalıştırılır, sonuç
karşılaştırılır — ama burada karşılaştırılan iki gerçekleştirim değil, aynı işlemin iki
ayrı kaynak üzerindeki davranışıdır. `java.io` akışlarıyla karıştırılmaması için burada
geçen her çıplak "akış" sözcüğü bu paragraftaki niteleyiciden sonra kullanılacak: veri
akışı zinciri, kısaca akış zinciri.

## Akış Zincirinin Sırası Kaynaktan Gelir, Arayüzden Değil

- **FN17** — Aynı beş sözcük iki kaynakta tutulur: sıralı bir `List` ve sırasız bir
  `HashSet`. İkisi üzerinde aynı akış zinciri çalıştırılır ve sonuçlar `equals` ile
  karşılaştırılır; ham gezinme sırası hiçbir yerde basılmaz.
- **FN18** — `sorted()` doğal sırayla çalışır; harf büyütme `Locale.ROOT` ile yapılır.
- **FN19** — `distinct()` ve `findFirst()` sıralı kaynak üzerinde denenir; sırasız kaynakla
  tekrarlanmaz, çünkü ölçülen şey ilk-görülen sıranın varlığıdır ve bu kavram sırasız
  kaynakta tanımsızdır.

```java
// AkisSira.java — akis zincirinin sirasi kaynaktan mi geliyor, arayuzden mi
import java.util.*;
import java.util.stream.*;

public class AkisSira {
    public static void main(String[] args) {
        List<String> siraliKaynak = List.of("armut", "kiraz", "elma", "uzum", "nar");
        Set<String> sirasizKaynak = new HashSet<>(siraliKaynak);

        List<String> sirali1 = siraliKaynak.stream().sorted().toList();
        List<String> sirali2 = sirasizKaynak.stream().sorted().toList();
        System.out.println("sorted(), sirali kaynakta alfabetik sira mi     : "
                + sirali1.equals(List.of("armut", "elma", "kiraz", "nar", "uzum")));
        System.out.println("sorted(), sirasiz kaynakta da ayni sonuc mu     : " + sirali2.equals(sirali1));

        List<String> beklenenBuyuk = List.of("ARMUT", "KIRAZ", "ELMA", "UZUM", "NAR");
        List<String> haritalananSirali = siraliKaynak.stream().map(x -> x.toUpperCase(Locale.ROOT)).toList();
        List<String> haritalananSirasiz = sirasizKaynak.stream().map(x -> x.toUpperCase(Locale.ROOT)).toList();
        System.out.println("map(), sirali kaynakta giris sirasini koruyor mu: " + haritalananSirali.equals(beklenenBuyuk));
        System.out.println("map(), sirasiz kaynakta giris sirasini koruyor mu: " + haritalananSirasiz.equals(beklenenBuyuk));

        List<String> beklenenUzun = List.of("armut", "kiraz", "elma", "uzum");
        List<String> filtrelenmis = siraliKaynak.stream().filter(x -> x.length() > 3).toList();
        System.out.println("filter(), sirali kaynakta sirayi koruyor mu     : " + filtrelenmis.equals(beklenenUzun));

        List<String> tekrarli = List.of("armut", "kiraz", "armut", "elma", "kiraz");
        List<String> tekil = tekrarli.stream().distinct().toList();
        System.out.println("distinct(), ilk gorulen sirayla korunuyor mu    : "
                + tekil.equals(List.of("armut", "kiraz", "elma")));

        Optional<String> ilk1 = siraliKaynak.stream().findFirst();
        Optional<String> ilk2 = siraliKaynak.stream().findFirst();
        System.out.println("findFirst(), iki ayri cagri ayni ogeyi mi veriyor: " + ilk1.equals(ilk2));
    }
}
```

```
sorted(), sirali kaynakta alfabetik sira mi     : true
sorted(), sirasiz kaynakta da ayni sonuc mu     : true
map(), sirali kaynakta giris sirasini koruyor mu: true
map(), sirasiz kaynakta giris sirasini koruyor mu: false
filter(), sirali kaynakta sirayi koruyor mu     : true
distinct(), ilk gorulen sirayla korunuyor mu    : true
findFirst(), iki ayri cagri ayni ogeyi mi veriyor: true
```

İlk iki satır bir kaynaktan bağımsız garantiyi ölçüyor: `sorted()` sıralı kaynakta alfabetik
sırayı üretiyor, sırasız `HashSet` kaynağında da **aynı** sonucu veriyor. Girdi hangi
kapta tutulursa tutulsun `sorted()` kendi sözünü tek başına yerine getiriyor — bu bir
**arayüz garantisi**dir, kaynağın kendi sırasına ihtiyaç duymaz.

Üçüncü ve dördüncü satır tam tersini gösteriyor. `map()` sıralı `List` kaynağında girişin
sırasını taşıyor (`true`), ama sırasız `HashSet` kaynağında **aynı** sözcüklerle çalıştığı
hâlde bu sırayı taşımıyor (`false`). `map()`'in kendisi hiç değişmedi; değişen kaynaktı.
Sıra sözü `map()`'e değil, altındaki kaba aittir — bu, dersin sınırlayıcı ölçümüdür ve
brifingin iddiasını doğruluyor: "akış sırayı korur" cümlesi kaynağın sözüdür, akışın değil.

Kalan üç satır aynı **kaynağa bağlı** kümeye giriyor. `filter()` sıralı kaynakta kalan
öğelerin sırasını koruyor; `distinct()` yinelenenler arasından ilk görüleni tutuyor ve
sırayı bozmuyor; `findFirst()` sıralı kaynakta iki ayrı çağrıda hep aynı öğeyi veriyor.
Üçünün de ortak noktası: sonucu belirleyen şey kaynağın **karşılaşma sırası**dır, ve bu
sıra yalnızca kaynak sıralı olduğunda vardır. Sırasız bir kaynakla tekrarlansalar
"ilk görülen" ya da "ilk öğe" kavramının kendisi tanımsızlaşır.

Bu ders "kaynağa bağlı" adını ayrı bir kova olarak kullanıyor, çünkü koleksiyonlar
konusundaki üçlü ayrımın orta terimi — **gerçekleştirim davranışı** — burada başka bir
seçime karşılık gelmiyor. Orada seçilen şey `HashMap` ile `TreeMap` gibi ayrı bir sınıftı;
burada `map()`'in kendisi hiç değişmiyor, tek değişen kaynağın **yapısıdır** (sıralı mı,
sırasız mı). Sonuç aynı yöne çıkıyor: gözlem çağrı yazımında görünmeyen bir üçüncü tarafın
elinde, ve o taraf bu konuda arayüz değil kaynaktır.

Aynı satırın bir de gevşek ucu var. `findFirst()` "sıralı kaynakta hep aynı öğeyi ver" der;
komşu yöntem `findAny()` böyle bir söz vermez, yalnızca eşleşen **bir** öğe döndürmeyi
garanti eder.

- **FN20** — `findFirst()` ile `findAny()` aynı sıralı kaynak üzerinde, aynı akış
  zincirinden ayrı ayrı çağrılır ve dönen değerler `equals` ile karşılaştırılır.

```java
// FindAny.java — findFirst ile findAny ayni sozu mu veriyor
import java.util.*;

public class FindAny {
    public static void main(String[] args) {
        List<String> kaynak = List.of("armut", "kiraz", "elma", "uzum", "nar");
        Optional<String> ilk = kaynak.stream().findFirst();
        Optional<String> herhangi = kaynak.stream().findAny();
        System.out.println("findFirst() sonucu: " + ilk.get());
        System.out.println("findAny() sonucu  : " + herhangi.get());
        System.out.println("ikisi ayni mi      : " + ilk.equals(herhangi));
    }
}
```

```
findFirst() sonucu: armut
findAny() sonucu  : armut
ikisi ayni mi      : true
```

Sıralı, tek iş parçacıklı bu akış zincirinde ikisi aynı öğeyi veriyor — API'nin
gevşekliği burada hiç görünmüyor, çünkü gevşekliğin fiilen kullanılması ancak akış
paralelleştirildiğinde söz konusu olur ve bu kurs akış paralelleştirmeyi ölçmüyor. Ölçülen
tek şey şu: aynı sonucu iki ayrı yöntemin iki ayrı sözle vermesi, sözlerin sıkılığının
sonuçtan bağımsız olabileceğini gösteriyor — `findAny()` daha az söz verir, ama bu daha az
sözün bedelini bu koşumda ödemiyoruz. `findAny()` tabloya girmiyor, çünkü verdiği tek söz
"eşleşen bir öğe" sözüdür ve bu söz zaten koşulsuzdur; kaynağa bağlı olan yalnız
`findFirst()`'ün "hangi öğe" sorusudur.

## Ara İşlem Sonlandırıcı Olmadan Hiç Çalışmaz

Bir akış zincirindeki `map`, `filter`, `sorted` gibi ara işlemler kurulur kurulmaz
çalışmaz; yalnızca zincire eklenirler. Bu, önceki bir konuda kurulan tembel değerlendirme
(lazy evaluation) kavramının burada sorulan hâlidir — kavram tekrar açılmıyor, sorulan
soru şu: bu tembellik akış zincirinin **sözü müdür**, yoksa yalnızca bir gerçekleştirim
ayrıntısı mıdır?

- **FN21** — Aynı ara işlem üç kez kurulur: hiç sonlandırıcı olmadan, `count()`
  sonlandırıcısıyla ve `toList()` sonlandırıcısıyla. Her seferinde bir sayaç, ara işlemin
  kaç kez çalıştığını sayar.

```java
// AkisTembellik.java — ara islem sonlandirici olmadan calisiyor mu
import java.util.*;
import java.util.stream.*;

public class AkisTembellik {
    public static void main(String[] args) {
        List<String> kaynak = List.of("armut", "kiraz", "elma", "uzum", "nar");

        int[] sayac1 = {0};
        Stream<String> boruHatti = kaynak.stream().peek(x -> sayac1[0]++);
        System.out.println("sonlandirici hic cagrilmadan calisan adim  : " + sayac1[0]);

        int[] sayac2 = {0};
        long uzunluk = kaynak.stream().peek(x -> sayac2[0]++).count();
        System.out.println("count() sonlandiricisiyla calisan adim     : " + sayac2[0] + " (uzunluk=" + uzunluk + ")");

        int[] sayac3 = {0};
        kaynak.stream().peek(x -> sayac3[0]++).toList();
        System.out.println("toList() sonlandiricisiyla calisan adim    : " + sayac3[0]);
    }
}
```

```
sonlandirici hic cagrilmadan calisan adim  : 0
count() sonlandiricisiyla calisan adim     : 0 (uzunluk=5)
toList() sonlandiricisiyla calisan adim    : 5
```

İlk satır beklenen sonucu veriyor: sonlandırıcı hiç çağrılmadığında ara işlem **hiç**
çalışmıyor, sayaç sıfırda kalıyor. Bu, koşuldan bağımsız bir garanti — akış zincirinin
kendi sözü — ve dördüncü konudaki tembel değerlendirmenin buradaki karşılığı bu kadardır.

İkinci satır bu garantiyi bir yönde daraltıyor. `count()` çağrıldığı hâlde sayaç yine
**sıfır**: uzunluk doğru hesaplandı (**5**) ama ara işlem hiç çalışmadı. Nedeni kaynağın
kendisinde: `List.of(...)`'un ürettiği liste boyutunu zaten bilir, ve `map`/`filter` gibi
öğe sayısını değiştirmeyen bir ara işlem zincire eklendiğinde akış zinciri sonucu tek tek
öğeleri ziyaret etmeden, doğrudan kaynağın boyutundan hesaplayabilir. `peek` de bu zincirin
bir parçası olduğu için, kaynağın boyutu üzerinden kısayoldan geçen bu hesap onu hiç
çağırmıyor. Üçüncü satır bunu doğruluyor: `toList()` sonucu üretmek için her öğeye
gerçekten ihtiyaç duyar, bu yüzden kısayol yoktur ve ara işlem **beş** kez çalışıyor.

Buradan çıkan sonuç ilk bakışta göründüğünden dardır: garanti edilen şey "sonlandırıcı
olmadan hiç çalışmama"dır, "sonlandırıcı çağrıldığında her öğe için çalışma" değildir.
İkincisi bir gerçekleştirim kararıdır — akış zinciri sonucu başka bir yoldan üretebiliyorsa
ara adımı atlayabilir. `peek` bu yüzden yalnızca gözlem ve hata ayıklama içindir; iş
mantığının bir parçası olarak kurulursa, çalışıp çalışmayacağı sonlandırıcıya bağlı kalır.

## Veri Akışı Tek Kullanımlıktır, Kaynağını Gezerken Büyütülemez

Son iki satır çağıranın kuralına giriyor: bu ikisi kaynağın türünden ya da akış zincirinin
biçiminden bağımsız, çağıranın uyması gereken iki kısıttır.

- **FN22** — Bir akış nesnesi iki kez tüketilir; ikinci tüketim ayrı bir `try` bloğunda
  denenir. Ayrı bir akış zincirinde, gezinme sırasında kaynak listesine bir öğe eklenir.

```java
// AkisKural.java — cagiranin iki kurali: tek kullanim ve gezerken buyutme
import java.util.*;
import java.util.stream.*;

public class AkisKural {
    public static void main(String[] args) {
        List<String> kaynak = List.of("armut", "kiraz", "elma", "uzum", "nar");

        Stream<String> tekKullanim = kaynak.stream();
        tekKullanim.count();
        try {
            tekKullanim.count();
            System.out.println("ayni akisi ikinci kez tuketmek: dustu");
        } catch (IllegalStateException e) {
            System.out.println("ayni akisi ikinci kez tuketmek -> istisna: " + e.getClass().getSimpleName());
        }

        List<String> genisleyen = new ArrayList<>(kaynak);
        try {
            genisleyen.stream().forEach(x -> {
                if (x.equals("elma")) genisleyen.add("mango");
            });
            System.out.println("gezerken kaynagi buyutmek: dustu, boyut=" + genisleyen.size());
        } catch (ConcurrentModificationException e) {
            System.out.println("gezerken kaynagi buyutmek -> istisna: " + e.getClass().getSimpleName());
        }
    }
}
```

```
ayni akisi ikinci kez tuketmek -> istisna: IllegalStateException
gezerken kaynagi buyutmek -> istisna: ConcurrentModificationException
```

İki kural da düştüğünde **istisna** veriyor, sessiz kalmıyor. `tekKullanim` üzerinde
`count()` bir kez çağrıldıktan sonra akış tüketilmiş sayılıyor; ikinci çağrı akışın
kendisini yeniden kullanmaya çalıştığı için `IllegalStateException` düşüyor — kaynağın
tipinden bağımsız, akış nesnesinin kendi durumundan kaynaklanan bir kural. Bu davranış bir
akış nesnesinin dahili olarak "tüketildi" diye işaretli bir bayrak taşıdığını gösteriyor:
bayrak bir kez kalkınca, üzerinde yeni bir sonlandırıcı çağırmak — hangisi olursa olsun --
aynı istisnayla karşılanıyor.

İkinci kural farklı bir mekanizmaya dayanıyor: `genisleyen` listesi `forEach` ile gezilirken
aynı liste büyütülüyor ve bu, listenin kendi değişiklik sayacını bozarak
`ConcurrentModificationException` düşürüyor — akışın sonlandırıcısı `forEach` olsa da,
düşen istisna aslında kaynağın kendi korumasıdır; akış zinciri burada yalnızca kaynağın
yineleyicisini kullanan bir aracıdır. Bu ikisi arasındaki fark önemlidir: birinci istisna
akış API'sinin **kendi** kuralıdır ve her akış nesnesinde geçerlidir, ikincisi ise akışın
**kaynağına** bağlıdır — değiştirilemez bir kaynaktan kurulan bir akış zinciri gezinirken
zaten hiç büyütülemeyeceği için bu kural orada hiç devreye girmez.

Ders boyunca ölçülen on satır tek tabloda toplanabilir:

| # | Ölçülen | Sonuç | Kaynağı |
|---|---|---|---|
| 1 | `sorted()`, sıralı kaynak | alfabetik sıra | arayüz garantisi |
| 2 | `sorted()`, sırasız kaynak | aynı sonuç | arayüz garantisi |
| 3 | `map()`, sıralı kaynak | sıra korunur | kaynağa bağlı |
| 4 | `map()`, sırasız kaynak | sıra korunmaz | kaynağa bağlı |
| 5 | `filter()`, sıralı kaynak | sıra korunur | kaynağa bağlı |
| 6 | `distinct()`, sıralı kaynak | ilk görülen korunur | kaynağa bağlı |
| 7 | `findFirst()`, sıralı kaynak | tutarlı sonuç | kaynağa bağlı |
| 8 | ara işlem, sonlandırıcısız | 0 çalışma | arayüz garantisi |
| 9 | aynı akışı ikinci tüketim | istisna | çağıranın kuralı |
| 10 | gezerken kaynağı büyütme | istisna | çağıranın kuralı |

Dağılım şöyle: **üçü** kaynaktan bağımsız bir garanti, **beşi** kaynağın ya da
sonlandırıcının seçimine bağlı, **ikisi** çağıranın uyması gereken ve kırıldığında
istisnayla düşen birer kuraldır. `count()`'un `peek`'i atlaması tabloya ayrı bir satır
olarak girmedi, çünkü ölçtüğü şey bir kaynağa bağlılık değil, sonlandırıcının kendi
seçimidir — sekizinci satırın bir alt sınırı olarak okunmalıdır. On işlemin çoğunluğu --
beşi — arayüzün değil kaynağın sözünü taşıyor; bu, önceki konunun kurduğu düzenle aynı
yöndedir: standart kütüphanenin görünen davranışının çoğu, konuştuğu arayüzün değil,
altındaki seçimin sözüdür.

## Özet

- Bir akış zincirinin sırayla ilgili sözü genellikle kaynaktan gelir: `map`, `filter`,
  `distinct`, `findFirst` yalnızca kaynak sıralıysa karşılaşma sırasını korur.
- `sorted()` kaynaktan bağımsız bir garantidir: sıralı ya da sırasız kaynakla aynı sonucu
  üretir. Sınırlayıcı ölçüm: "akış sırayı korur" cümlesi çoğunlukla kaynağın sözüdür.
- Bir ara işlem sonlandırıcı olmadan hiç çalışmaz; bu koşulsuz bir garantidir. Ama
  sonlandırıcı çağrıldığında her öğenin ziyaret edileceği garanti değildir — `count()`
  ara işlemi tamamen atlayabilir, `toList()` atlayamaz.
- Aynı akışı ikinci kez tüketmek `IllegalStateException` ile düşer; bu kaynağın tipinden
  bağımsız, akış nesnesinin kendi durumuna ait bir kuraldır.
- Akışı gezerken kaynak koleksiyonu büyütmek `ConcurrentModificationException` ile düşer;
  düşen istisna akışın değil, altındaki koleksiyonun korumasıdır.
- On ölçümün üçü kaynaktan bağımsız garanti, beşi kaynağa bağlı, ikisi çağıranın kuralıdır;
  çoğunluk kaynağa bağlı kalmıştır.

## Sonraki Adım

Bu ders bir akış zincirinin ne söylediğini, ne söylemediğini ölçtü: sıra çoğunlukla
kaynağın sözüdür, tembellik koşulsuz bir garantidir ama her öğenin ziyaret edileceği
değildir, ve iki kural — tek kullanım, gezerken büyütmeme — doğrudan çağırana düşer.
Zincirin ucunda genellikle tek bir değer değil, bir kap birikir — bir liste, bir sözlük,
bir sayı — ve bu derste hep hazır toplama yöntemleri (`toList()`, `count()`) kullanıldı.
Sıradaki ders o kabı kuran parçaya bakar: toplayıcının hangi sözü verdiğini, sonuç kabının
tipinin arayüzün sözü olup olmadığını ve yinelenen bir anahtarla karşılaşıldığında hangi
toplayıcının istisna verip hangisinin birleştirdiğini ölçer.
