Stream API
Generics, wildcards ve lambda'yı gördük. Şimdi hepsini bir araya getiren, Java 8'in en güçlü özelliği: Stream API. Bir koleksiyondaki veriyi sorgulamak, dönüştürmek, filtrelemek — hepsi tek bir akıcı pipeline ile.
Stream Nedir?
Bir nehir düşün. Nehirden su akıyor. Sen nehrin kenarında oturuyorsun ve akan suya filtreler, dönüştürücüler koyuyorsun. Kirli su giriyor, temiz su çıkıyor. Ama nehrin kendisini değiştirmiyorsun — sadece akışı yönlendiriyorsun.
Stream tam olarak bu. Veri kaynağından (koleksiyon, dizi, dosya) akan verileri, zincirleme operasyonlarla işleyip sonuç üretiyorsun. Orijinal veri asla değişmez.
List<String> isimler = List.of("Ali", "Veli", "Ayşe", "Zeynep", "Burak");
List<String> sonuc = isimler.stream() // Akış başla
.filter(s -> s.length() > 3) // 3 harften uzun olanlar
.map(String::toUpperCase) // Büyük harfe çevir
.sorted() // Sırala
.toList(); // Listeye topla
System.out.println(sonuc); // [AYŞE, BURAK, ZEYNEP]
System.out.println(isimler); // [Ali, Veli, Ayşe, Zeynep, Burak] — değişmedi!Stream Oluşturma
Stream'i farklı kaynaklardan oluşturabilirsin:
// 1. Koleksiyondan
List<String> liste = List.of("a", "b", "c");
Stream<String> s1 = liste.stream();
// 2. Diziden
String[] dizi = {"x", "y", "z"};
Stream<String> s2 = Arrays.stream(dizi);
// 3. Doğrudan değerlerden
Stream<String> s3 = Stream.of("merhaba", "dünya");
// 4. Sayı aralığından
IntStream s4 = IntStream.range(1, 10); // 1-9
IntStream s5 = IntStream.rangeClosed(1, 10); // 1-10
// 5. Boş stream
Stream<String> s6 = Stream.empty();
// 6. Sonsuz stream (dikkat!)
Stream<Double> s7 = Stream.generate(Math::random); // Sonsuz rastgele sayı
Stream<Integer> s8 = Stream.iterate(0, n -> n + 2); // 0, 2, 4, 6, ...
// 7. Sınırlı iterate (Java 9+)
Stream<Integer> s9 = Stream.iterate(0, n -> n < 100, n -> n + 2); // 0, 2, ..., 98⚠️ Sonsuz stream'lere `limit()` eklemeyi unutma! Yoksa program sonsuza kadar çalışır.
List<Double> rastgele = Stream.generate(Math::random)
.limit(5) // 5 tane al
.toList();Intermediate vs Terminal Operasyonlar
Stream operasyonları iki kategoriye ayrılır:
| Tür | Özellik | Örnekler |
|---|---|---|
| Intermediate | Stream döner, zincirlenir | filter, map, sorted, distinct, limit, skip |
| Terminal | Sonuç üretir, stream biter | collect, forEach, count, reduce, findFirst, anyMatch |
Önemli kural: Terminal operasyon çağrılana kadar intermediate operasyonlar çalışmaz. Buna lazy evaluation denir.
// Bu satır HİÇBİR ŞEY yapmaz — terminal operasyon yok!
Stream<String> stream = isimler.stream()
.filter(s -> {
System.out.println("Filtre: " + s); // Bu print çalışmaz!
return s.length() > 3;
});
// Terminal operasyon eklenince çalışır
long sayi = stream.count(); // Şimdi filtre çalışır💡 Lazy evaluation neden iyi? Gereksiz işlem yapmaz. 1 milyon elemanlı listede
filter().findFirst()dersen, ilk eşleşmede durur — 1 milyon elemanı filtrelemez.
filter — Filtrele
Predicate alır, koşulu sağlayanları geçirir:
List<Integer> sayilar = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// Çift sayılar
List<Integer> ciftler = sayilar.stream()
.filter(n -> n % 2 == 0)
.toList();
System.out.println(ciftler); // [2, 4, 6, 8, 10]
// Birden fazla koşul
List<Integer> buyukCiftler = sayilar.stream()
.filter(n -> n % 2 == 0)
.filter(n -> n > 5)
.toList();
System.out.println(buyukCiftler); // [6, 8, 10]map — Dönüştür
Function alır, her elemanı dönüştürür:
List<String> isimler = List.of("ali", "veli", "ayşe");
// Büyük harfe çevir
List<String> buyuk = isimler.stream()
.map(String::toUpperCase)
.toList();
System.out.println(buyuk); // [ALİ, VELİ, AYŞE]
// Uzunluklara çevir
List<Integer> uzunluklar = isimler.stream()
.map(String::length)
.toList();
System.out.println(uzunluklar); // [3, 4, 4]mapToInt, mapToDouble, mapToLong
Primitive stream'lere dönüştürmek için (boxing maliyetinden kaçınır):
List<String> kelimeler = List.of("Java", "Python", "Go");
int toplamUzunluk = kelimeler.stream()
.mapToInt(String::length)
.sum(); // IntStream'in özel metodu
System.out.println(toplamUzunluk); // 12
OptionalDouble ortalama = kelimeler.stream()
.mapToInt(String::length)
.average();
System.out.println(ortalama.orElse(0)); // 4.0flatMap — Düzleştir
İç içe koleksiyonları tek seviyeye indirir:
List<List<String>> icIce = List.of(
List.of("Ali", "Veli"),
List.of("Ayşe", "Fatma"),
List.of("Burak")
);
// map kullanırsan: Stream<List<String>> — hâlâ iç içe
// flatMap kullanırsan: Stream<String> — düz
List<String> duz = icIce.stream()
.flatMap(Collection::stream) // Her alt listeyi stream'e çevir ve düzleştir
.toList();
System.out.println(duz); // [Ali, Veli, Ayşe, Fatma, Burak]Gerçek hayat örneği — her öğrencinin aldığı dersleri düzleştirmek:
List<Ogrenci> ogrenciler = List.of(
new Ogrenci("Ali", List.of("Java", "Python")),
new Ogrenci("Veli", List.of("Java", "C++"))
);
List<String> tumDersler = ogrenciler.stream()
.flatMap(o -> o.getDersler().stream())
.distinct() // Tekrarları kaldır
.toList();
System.out.println(tumDersler); // [Java, Python, C++]sorted — Sırala
List<String> isimler = List.of("Zeynep", "Ali", "Burak", "Ayşe");
// Doğal sıralama
List<String> sirali = isimler.stream()
.sorted()
.toList();
System.out.println(sirali); // [Ali, Ayşe, Burak, Zeynep]
// Ters sıralama
List<String> ters = isimler.stream()
.sorted(Comparator.reverseOrder())
.toList();
// Uzunluğa göre sırala
List<String> uzunluga = isimler.stream()
.sorted(Comparator.comparingInt(String::length))
.toList();
System.out.println(uzunluga); // [Ali, Ayşe, Burak, Zeynep]distinct, limit, skip
List<Integer> sayilar = List.of(1, 2, 2, 3, 3, 3, 4, 5);
// Tekrarları kaldır
List<Integer> benzersiz = sayilar.stream()
.distinct()
.toList();
System.out.println(benzersiz); // [1, 2, 3, 4, 5]
// İlk 3'ü al
List<Integer> ilkUc = sayilar.stream()
.limit(3)
.toList();
System.out.println(ilkUc); // [1, 2, 2]
// İlk 3'ü atla
List<Integer> kalanlar = sayilar.stream()
.skip(3)
.toList();
System.out.println(kalanlar); // [3, 3, 3, 4, 5]
// Sayfalama (skip + limit)
int sayfa = 2, sayfaBoyutu = 3;
List<Integer> sayfaVerisi = sayilar.stream()
.skip((long) (sayfa - 1) * sayfaBoyutu)
.limit(sayfaBoyutu)
.toList();
System.out.println(sayfaVerisi); // [3, 3, 3]reduce — İndirge
Tüm elemanları tek bir değere indirger:
List<Integer> sayilar = List.of(1, 2, 3, 4, 5);
// Toplam
int toplam = sayilar.stream()
.reduce(0, (a, b) -> a + b);
System.out.println(toplam); // 15
// Method reference ile
int toplam2 = sayilar.stream()
.reduce(0, Integer::sum);
// Çarpım
int carpim = sayilar.stream()
.reduce(1, (a, b) -> a * b);
System.out.println(carpim); // 120
// Başlangıç değeri olmadan (Optional döner)
Optional<Integer> max = sayilar.stream()
.reduce(Integer::max);
max.ifPresent(m -> System.out.println("Max: " + m)); // Max: 5String Birleştirme
List<String> kelimeler = List.of("Java", "çok", "güzel");
String cumle = kelimeler.stream()
.reduce("", (a, b) -> a + " " + b)
.trim();
System.out.println(cumle); // Java çok güzel
// Daha iyi yol — String.join veya Collectors.joining
String cumle2 = String.join(" ", kelimeler);
String cumle3 = kelimeler.stream()
.collect(Collectors.joining(" "));collect — Topla
collect, stream'in sonuçlarını bir koleksiyona veya başka bir yapıya toplar. En sık kullandığın terminal operasyon.
Temel Collectors
List<String> isimler = List.of("Ali", "Veli", "Ayşe", "Ali", "Burak");
// Listeye topla
List<String> liste = isimler.stream()
.filter(s -> s.length() > 3)
.collect(Collectors.toList());
// Java 16+ kısa yol (unmodifiable list döner)
List<String> liste2 = isimler.stream()
.filter(s -> s.length() > 3)
.toList();
// Set'e topla (tekrarlar silinir)
Set<String> set = isimler.stream()
.collect(Collectors.toSet());
System.out.println(set); // [Ali, Veli, Ayşe, Burak]
// String birleştir
String birlesik = isimler.stream()
.collect(Collectors.joining(", "));
System.out.println(birlesik); // Ali, Veli, Ayşe, Ali, Burak
// Virgül + prefix/suffix
String formatli = isimler.stream()
.collect(Collectors.joining(", ", "[", "]"));
System.out.println(formatli); // [Ali, Veli, Ayşe, Ali, Burak]groupingBy — Grupla
SQL'deki GROUP BY gibi:
List<String> isimler = List.of("Ali", "Ayşe", "Burak", "Berk", "Can");
// İlk harfe göre grupla
Map<Character, List<String>> gruplar = isimler.stream()
.collect(Collectors.groupingBy(s -> s.charAt(0)));
System.out.println(gruplar);
// {A=[Ali, Ayşe], B=[Burak, Berk], C=[Can]}// Uzunluğa göre grupla ve say
Map<Integer, Long> uzunlukSayisi = isimler.stream()
.collect(Collectors.groupingBy(
String::length,
Collectors.counting()
));
System.out.println(uzunlukSayisi); // {3=[Ali, Can], 4=[Ayşe, Berk], 5=[Burak]}partitioningBy — İkiye Böl
Predicate'e göre true/false olarak ikiye ayırır:
List<Integer> sayilar = List.of(1, 2, 3, 4, 5, 6);
Map<Boolean, List<Integer>> bolunmus = sayilar.stream()
.collect(Collectors.partitioningBy(n -> n % 2 == 0));
System.out.println(bolunmus);
// {false=[1, 3, 5], true=[2, 4, 6]}toMap — Map'e Topla
List<String> isimler = List.of("Ali", "Veli", "Ayşe");
Map<String, Integer> isimUzunluk = isimler.stream()
.collect(Collectors.toMap(
s -> s, // key
String::length // value
));
System.out.println(isimUzunluk); // {Ali=3, Veli=4, Ayşe=4}⚠️ `toMap` ile aynı key varsa `IllegalStateException` fırlatır! Merge fonksiyonu ekle:
List<String> kelimeler = List.of("ali", "ay", "araba", "bal", "bir");
Map<Character, String> ilkHarf = kelimeler.stream()
.collect(Collectors.toMap(
s -> s.charAt(0),
s -> s,
(var1, var2) -> var1 + ", " + var2 // Çakışmada birleştir
));
System.out.println(ilkHarf); // {a=ali, ay, araba, b=bal, bir}Summarizing — İstatistik
List<Integer> notlar = List.of(85, 90, 78, 92, 88);
IntSummaryStatistics istat = notlar.stream()
.collect(Collectors.summarizingInt(Integer::intValue));
System.out.println("Sayı: " + istat.getCount()); // 5
System.out.println("Toplam: " + istat.getSum()); // 433
System.out.println("Min: " + istat.getMin()); // 78
System.out.println("Max: " + istat.getMax()); // 92
System.out.println("Ortalama: " + istat.getAverage()); // 86.6Terminal Operasyonlar — Diğerleri
forEach
List<String> isimler = List.of("Ali", "Veli", "Ayşe");
isimler.stream().forEach(System.out::println);
// Kısa yol — doğrudan List üzerinde
isimler.forEach(System.out::println);count
long uzunIsimSayisi = isimler.stream()
.filter(s -> s.length() > 3)
.count();findFirst, findAny
Optional<String> ilk = isimler.stream()
.filter(s -> s.startsWith("A"))
.findFirst();
ilk.ifPresent(System.out::println); // Ali
// findAny — parallel stream'de daha hızlı
Optional<String> herhangi = isimler.stream()
.filter(s -> s.startsWith("A"))
.findAny();anyMatch, allMatch, noneMatch
List<Integer> sayilar = List.of(2, 4, 6, 8);
boolean hepsiCift = sayilar.stream().allMatch(n -> n % 2 == 0); // true
boolean tekVarMi = sayilar.stream().anyMatch(n -> n % 2 != 0); // false
boolean negatifYok = sayilar.stream().noneMatch(n -> n < 0); // truemin, max
Optional<Integer> min = sayilar.stream().min(Comparator.naturalOrder());
Optional<Integer> max = sayilar.stream().max(Comparator.naturalOrder());Parallel Stream
Stream operasyonlarını birden fazla thread'de çalıştırır:
List<Integer> buyukListe = IntStream.rangeClosed(1, 1_000_000)
.boxed()
.toList();
// Sequential
long baslangic = System.currentTimeMillis();
long toplam1 = buyukListe.stream()
.mapToLong(Integer::longValue)
.sum();
System.out.println("Sequential: " + (System.currentTimeMillis() - baslangic) + "ms");
// Parallel
baslangic = System.currentTimeMillis();
long toplam2 = buyukListe.parallelStream()
.mapToLong(Integer::longValue)
.sum();
System.out.println("Parallel: " + (System.currentTimeMillis() - baslangic) + "ms");Parallel stream oluşturma yolları:
// Yol 1: parallelStream()
liste.parallelStream()...
// Yol 2: stream().parallel()
liste.stream().parallel()...⚠️ Parallel stream her zaman daha hızlı DEĞİLDİR! Thread oluşturma ve koordinasyon maliyeti var. Küçük veri setlerinde sequential daha hızlı. Parallel stream şu durumlarda işe yarar: - Büyük veri (100.000+ eleman) - CPU-yoğun operasyonlar - Shared state yok (yan etkisiz operasyonlar)
Lazy Evaluation Detaylı
Stream'in en önemli optimizasyonlarından biri:
List<Integer> sayilar = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
Optional<Integer> ilkBuyuk = sayilar.stream()
.filter(n -> {
System.out.println("Filter: " + n);
return n > 5;
})
.map(n -> {
System.out.println("Map: " + n);
return n * 10;
})
.findFirst();Çıktı:
Filter: 1
Filter: 2
Filter: 3
Filter: 4
Filter: 5
Filter: 6
Map: 66'yı bulunca durdu! 7, 8, 9, 10'a bakmadı bile. Bu short-circuit evaluation.
💡 Short-circuit operasyonlar:
findFirst,findAny,anyMatch,allMatch,noneMatch,limit. Bu operasyonlar, sonucu bulmak için tüm stream'i işlemek zorunda değil.
Stream Tek Kullanımlık
Bir stream sadece bir kez kullanılabilir:
Stream<String> stream = isimler.stream();
stream.forEach(System.out::println); // ✅ İlk kullanım
// HATA! IllegalStateException: stream has already been operated upon
stream.forEach(System.out::println); // ❌ İkinci kullanımHer seferinde yeni stream oluştur:
isimler.stream().forEach(System.out::println); // ✅
isimler.stream().count(); // ✅ Yeni streamGerçek Hayat Örnekleri
E-ticaret Senaryosu
record Urun(String isim, String kategori, double fiyat, int stok) {}
List<Urun> urunler = List.of(
new Urun("Laptop", "Elektronik", 15000, 10),
new Urun("Telefon", "Elektronik", 8000, 25),
new Urun("Kulaklık", "Elektronik", 500, 100),
new Urun("Masa", "Mobilya", 3000, 15),
new Urun("Sandalye", "Mobilya", 1500, 30)
);
// Kategori bazlı toplam stok
Map<String, Integer> stoklar = urunler.stream()
.collect(Collectors.groupingBy(
Urun::kategori,
Collectors.summingInt(Urun::stok)
));
System.out.println(stoklar); // {Elektronik=135, Mobilya=45}
// En pahalı 3 ürün
List<String> pahalilar = urunler.stream()
.sorted(Comparator.comparingDouble(Urun::fiyat).reversed())
.limit(3)
.map(Urun::isim)
.toList();
System.out.println(pahalilar); // [Laptop, Telefon, Masa]
// Stokta olan elektronik ürünlerin ortalama fiyatı
double ortFiyat = urunler.stream()
.filter(u -> u.kategori().equals("Elektronik"))
.filter(u -> u.stok() > 0)
.mapToDouble(Urun::fiyat)
.average()
.orElse(0);
System.out.println("Ort. fiyat: " + ortFiyat); // 7833.33Kelime Frekansı
String metin = "java güzel java kolay java eğlenceli güzel kolay";
Map<String, Long> frekans = Arrays.stream(metin.split(" "))
.collect(Collectors.groupingBy(
Function.identity(),
Collectors.counting()
));
System.out.println(frekans);
// {java=3, güzel=2, kolay=2, eğlenceli=1}Optional ile Stream
Stream operasyonları sıklıkla Optional döner:
List<String> isimler = List.of("Ali", "Veli", "Ayşe");
// findFirst → Optional
isimler.stream()
.filter(s -> s.startsWith("Z"))
.findFirst()
.ifPresentOrElse(
s -> System.out.println("Buldum: " + s),
() -> System.out.println("Bulamadım")
);
// Optional'ı stream'e çevir (Java 9+)
Optional<String> opt = Optional.of("Merhaba");
List<String> liste = opt.stream().toList(); // [Merhaba]
Optional<String> bos = Optional.empty();
List<String> bosListe = bos.stream().toList(); // []Stream vs For Loop — Ne Zaman Hangisi?
| Durum | Tercih |
|---|---|
| Basit dönüştürme/filtreleme | Stream |
| Karmaşık durum yönetimi (index, break, continue) | For loop |
| Okunabilirlik önemli | Stream (genellikle) |
| Performans kritik | For loop (çok küçük fark) |
| Paralel işlem gerekli | Stream |
// STREAM — temiz ve okunabilir
List<String> sonuc = isimler.stream()
.filter(s -> s.length() > 3)
.map(String::toUpperCase)
.sorted()
.toList();
// FOR LOOP — aynı iş, daha fazla kod
List<String> sonuc = new ArrayList<>();
for (String s : isimler) {
if (s.length() > 3) {
sonuc.add(s.toUpperCase());
}
}
Collections.sort(sonuc);Özet
Stream, koleksiyonları fonksiyonel tarzda işlemenin yolu — orijinal veriyi değiştirmez
Intermediate operasyonlar (filter, map, sorted) zincirlenir ve lazy çalışır
Terminal operasyonlar (collect, forEach, reduce) stream'i tetikler ve sonuç üretir
collect ile
toList(),toSet(),groupingBy(),joining()gibi toplayıcılar kullanılırParallel stream büyük veri setlerinde performans kazandırır ama her zaman daha hızlı değildir
Stream tek kullanımlıktır — bir kez consume edildikten sonra tekrar kullanılamaz
AI Asistan
Sorularını yanıtlamaya hazır