Eğitim Portalı/Sürüm Analizi/Java 23 — Sürüm Analizi ve Özellikler
Sürüm Analizi05-java-versiyon-analizi/16-Java-23

Java 23 — Sürüm Analizi ve Özellikler

> Bu doküman Java 23 sürümünü Türkçe olarak detaylı şekilde açıklar. Hedef kitle: > Java geliştiricileri, eğitmenler ve sürümler arası geçişi planlayanlar.

Java 23 — Sürüm Analizi ve Özellikler

Bu doküman Java 23 sürümünü Türkçe olarak detaylı şekilde açıklar. Hedef kitle: Java geliştiricileri, eğitmenler ve sürümler arası geçişi planlayanlar.


1. Sürüm Bilgisi

ÖzellikDeğer
SürümJava SE 23 (JDK 23)
Çıkış Tarihi17 Eylül 2024
Destek Türünon-LTS (Long Term Support DEĞİL)
Önceki LTSJava 21 (Eylül 2023)
Sonraki LTSJava 25 (Eylül 2025 — planlanan)
Destek SüresiGenellikle 6 ay (bir sonraki feature sürümüne kadar)
JDK Şeması6 aylık feature-release kadansı devam ediyor

Önemli not — non-LTS ne demek? Java 23 bir "feature release" (özellik sürümü) olup yalnızca bir sonraki sürüm (Java 24) çıkana kadar (yaklaşık 6 ay) güncelleme alır. Production ortamları için genellikle LTS sürümleri (21, 25, ...) tercih edilir. Java 23, yeni özellikleri denemek ve preview API'lerle çalışmak için idealdir.

Preview Durumu Açıklaması

Java'da bir özellik birden çok aşamadan geçer:

  1. Preview (1., 2., 3. tur) — Tasarımı tamamlanmış ama henüz kalıcı olmayan, geri bildirimle değişebilen özellikler. Kullanmak için derleme ve çalıştırmada --enable-preview bayrağı gerekir.
  2. Final / Standart — Kalıcı hale gelmiş, bayrak gerektirmeyen özellikler.

Java 23'te preview özelliklerini derlemek için:

# Derleme
javac --release 23 --enable-preview Ornek.java

# Çalıştırma
java --enable-preview Ornek

2. Java 23 JEP'leri (Genel Bakış)

JEPÖzellikDurum
JEP 467Markdown Documentation CommentsFinal / Kalıcı (preview değil)
JEP 473Stream Gatherers2. Preview
JEP 480Structured Concurrency3. Preview
JEP 481Scoped Values3. Preview
JEP 455Primitive Types in Patterns, instanceof, switchPreview
JEP 474ZGC: Generational Mode by DefaultFinal (davranış değişikliği)
JEP 476Module Import DeclarationsPreview
JEP 482Flexible Constructor Bodies2. Preview
JEP 471Deprecate the Memory-Access Methods in sun.misc.UnsafeFinal

Bu README her bir önemli özelliği aşağıda detaylandırır. Bazı özellikler kendi ayrı dosyalarında derinlemesine ele alınmıştır:


3. JEP 467 — Markdown Documentation Comments (Final)

Detaylı örnekler için bkz. MarkdownJavadoc.md

NEDİR?

Javadoc yorumlarını artık HTML + @tag karışımı yerine Markdown ile yazabilmenizi sağlayan özellik. Yeni yorum sözdizimi /// (üç eğik çizgi) ile başlar.

/// Bu bir **Markdown** Javadoc yorumudur.
/// - Madde 1
/// - Madde 2
///
/// Bağlantı: [String]
public class Ornek { }

NEDEN GELDİ?

Klasik Javadoc HTML tabanlıdır; <p>, <ul>, <code> gibi etiketler okunması zor ve hata yapmaya açıktır. Markdown çok daha okunabilir ve geliştiricilerin zaten README'lerde kullandığı bir formattır.

NE İŞE YARAR?

  • Dokümantasyon yorumlarını kaynak kodda daha okunabilir hale getirir.
  • HTML kaçış karakterleriyle (&lt;, &gt;) uğraşmayı azaltır.
  • Kod blokları, listeler, tablolar, bağlantılar daha kolay yazılır.

NEREDE KOLAYLIK SAĞLAR?

API dokümantasyonu yazan kütüphane geliştiricileri, dahili kod dokümantasyonu tutan ekipler.

ESKİ vs YENİ (kısa)

// ESKİ (HTML tabanlı)
/**
 * Toplama yapar.
 * <p>Örnek: <code>topla(2, 3)</code> → 5</p>
 * <ul><li>Negatif sayı kabul eder</li></ul>
 */

// YENİ (Markdown tabanlı)
/// Toplama yapar.
///
/// Örnek: `topla(2, 3)` → 5
///
/// - Negatif sayı kabul eder

GERÇEK HAYAT ÖRNEĞİ

Bir açık kaynak kütüphane (örn. bir JSON parser) API dokümantasyonunu Markdown'a geçirerek hem kaynak kodda hem de üretilen HTML'de daha temiz dokümantasyon sunar.


4. JEP 473 — Stream Gatherers (2. Preview)

Çalıştırılabilir gelişmiş örnekler için bkz. StreamGatherersGelismis.java

NEDİR?

Stream API'sine özel ara (intermediate) operasyonlar tanımlama yeteneği ekleyen özellik. stream.gather(...) metodu ile Gatherer arayüzünü kullanırız. Collectors'ın terminal operasyonlar için yaptığını, Gatherer ara operasyonlar için yapar.

NEDEN GELDİ?

map, filter, flatMap gibi yerleşik ara operasyonlar her ihtiyacı karşılamıyordu. Örneğin "kayan pencere" (sliding window), "çalışan toplam" (running sum), gruplayıp tekilleştirme gibi durumsal (stateful) operasyonları standart API ile yazmak zordu.

NE İŞE YARAR?

  • Durumlu (stateful) ara operasyonlar yazılabilir.
  • Yerleşik fabrikalar: Gatherers.windowFixed(n), Gatherers.windowSliding(n), Gatherers.fold(...), Gatherers.scan(...), Gatherers.mapConcurrent(...).
  • Birden çok elemanı tek bir elemana ya da bir elemanı birden çoğa dönüştürebilir.

NEREDE KOLAYLIK SAĞLAR?

Veri işleme hatları (data pipelines), zaman serisi analizi (hareketli ortalama), olay akışı işleme, batch'leme.

ESKİ vs YENİ

// ESKİ: Kayan pencere için elle index yönetimi gerekirdi
List<List<Integer>> pencereler = new ArrayList<>();
for (int i = 0; i + 3 <= liste.size(); i++) {
    pencereler.add(liste.subList(i, i + 3));
}

// YENİ: Tek satır
var pencereler = liste.stream()
    .gather(Gatherers.windowSliding(3))
    .toList();

GERÇEK HAYAT ÖRNEĞİ

Borsa fiyatlarının 5 günlük hareketli ortalamasını hesaplamak: fiyatlar.stream().gather(Gatherers.windowSliding(5)).map(ortalama).toList(). Detaylı örnek .java dosyasındadır.

AVANTAJ / DEZAVANTAJ / RİSK

  • Avantaj: Çok daha okunabilir, yeniden kullanılabilir akış operasyonları.
  • Dezavantaj/Risk: Hâlâ preview — API imzaları değişebilir, --enable-preview gerektirir, production'da dikkatli kullanılmalı.

5. JEP 480 — Structured Concurrency (3. Preview)

NEDİR?

Birden fazla eşzamanlı görevi tek bir iş birimi gibi yöneten API. java.util.concurrent.StructuredTaskScope ile alt görevler bir kapsam (scope) içinde başlatılır, hepsi birlikte tamamlanır veya birlikte iptal edilir.

NEDEN GELDİ?

Klasik ExecutorService + Future kullanımında görev sızıntısı (leak), iptal yönetimi ve hata yayılımı (error propagation) hataya açıktır. Bir görev başarısız olsa bile diğerleri arka planda boşuna çalışmaya devam edebilir.

NE İŞE YARAR?

  • Alt görevlerin yaşam döngüsünü kod bloğunun yapısına bağlar (try-with-resources gibi).
  • "Birini başaranı bekle" (ShutdownOnSuccess) ya da "ilk hatada hepsini iptal et" (ShutdownOnFailure) gibi politikalar sunar.

NEREDE KOLAYLIK SAĞLAR?

Mikroservis çağrılarının paralel yürütülmesi, fan-out/fan-in desenleri, birden çok kaynaktan veri toplama.

ESKİ vs YENİ

// ESKİ
ExecutorService ex = Executors.newVirtualThreadPerTaskExecutor();
Future<String> kullanici = ex.submit(() -> kullaniciGetir());
Future<Integer> siparis  = ex.submit(() -> siparisGetir());
// İptal/hata yönetimi elle, sızıntı riski yüksek

// YENİ (preview)
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
    var kullanici = scope.fork(() -> kullaniciGetir());
    var siparis   = scope.fork(() -> siparisGetir());
    scope.join().throwIfFailed();
    return birlestir(kullanici.get(), siparis.get());
}

GERÇEK HAYAT ÖRNEĞİ

Bir ürün detay sayfası; ürün bilgisi, stok durumu ve yorumları üç ayrı servisten paralel çeker. Biri başarısız olursa hepsi iptal edilir, kaynak israfı olmaz.

Risk: Preview API. StructuredTaskScope imzası sürümler arasında değişebilir (nitekim ileri sürümlerde değişti). --enable-preview şarttır.


6. JEP 481 — Scoped Values (3. Preview)

NEDİR?

Bir veriyi, onu açıkça parametre olarak geçirmeden, belirli bir yürütme kapsamı (ve onun alt görevleri) içinde paylaşmayı sağlayan değişmez (immutable) yapı. ScopedValue sınıfı kullanılır.

NEDEN GELDİ?

ThreadLocal'ın problemleri vardı: değiştirilebilir (mutable), temizlenmesi zor (memory leak riski), milyonlarca sanal iş parçacığında (virtual thread) maliyetli. ScopedValue bu sorunları çözer.

NE İŞE YARAR?

  • Değer değişmezdir; where(...).run(...) bloğu boyunca geçerlidir.
  • Blok bittiğinde otomatik temizlenir.
  • Sanal iş parçacıklarıyla verimli çalışır, structured concurrency ile uyumludur.

NEREDE KOLAYLIK SAĞLAR?

İstek bağlamı (request context), kullanıcı kimliği, transaction ID gibi "ortam" bilgisini metot zincirleri boyunca taşımak.

ESKİ vs YENİ

// ESKİ: ThreadLocal
static final ThreadLocal<User> CURRENT = new ThreadLocal<>();
CURRENT.set(user);
try { isYap(); } finally { CURRENT.remove(); } // unutulursa leak!

// YENİ: ScopedValue (preview)
static final ScopedValue<User> CURRENT = ScopedValue.newInstance();
ScopedValue.where(CURRENT, user).run(() -> isYap()); // otomatik temizlik

GERÇEK HAYAT ÖRNEĞİ

Web sunucusunda gelen her HTTP isteği için kimliği doğrulanmış kullanıcıyı ScopedValue ile tutmak; istek işleme zincirindeki tüm metotlar bu değere güvenli ve değişmez şekilde erişir.

Risk: Preview. --enable-preview gerekir.


7. JEP 455 — Primitive Types in Patterns, instanceof, switch (Preview)

NEDİR?

İlkel (primitive) tiplerin (int, long, double, ...) artık instanceof, desen eşleştirme (pattern matching) ve switch içinde kullanılabilmesi.

NEDEN GELDİ?

Önceki desen eşleştirme yalnızca referans tipleri destekliyordu. İlkel tipler için tip kontrolü ve güvenli dönüşüm (özellikle daralma — narrowing) dil seviyesinde yoktu.

NE İŞE YARAR?

  • if (x instanceof int i) gibi ilkel tip kontrolü.
  • switch içinde ilkel desenler ve daralma kontrolleri (örn. bir int değer byte aralığına sığıyor mu?).

ESKİ vs YENİ

// ESKİ: elle aralık kontrolü
int x = 200;
if (x >= Byte.MIN_VALUE && x <= Byte.MAX_VALUE) {
    byte b = (byte) x;
}

// YENİ (preview): desen ile güvenli daralma
if (x instanceof byte b) {
    // x, byte aralığına sığıyorsa b atanır
}

GERÇEK HAYAT ÖRNEĞİ

Bir veri dönüştürme katmanında, gelen sayısal değerlerin hedef ilkel tipe güvenli şekilde sığıp sığmadığını desenlerle kontrol etmek.

Risk: Preview. --enable-preview gerekir.


8. JEP 474 — ZGC: Generational Mode by Default (Final)

NEDİR?

Z Garbage Collector (ZGC) artık varsayılan olarak generational (kuşaksal) modda çalışır. Eski (non-generational) mod hâlâ seçilebilir ama önerilmez.

NEDEN GELDİ?

Generational hipotezi ("çoğu nesne genç ölür") sayesinde nesneleri genç (young) ve yaşlı (old) kuşaklara ayırarak GC daha az CPU ve bellek harcar. Java 21'de generational ZGC eklenmişti; Java 23'te bu artık varsayılan.

NE İŞE YARAR?

  • Çok büyük heap'lerde (TB seviyesi) düşük duraklama (low pause) süreleri.
  • Daha az bellek ve CPU yükü, daha yüksek verim (throughput).

ESKİ vs YENİ

# Java 21–22: generational ZGC açıkça istenirdi
java -XX:+UseZGC -XX:+ZGenerational uygulama

# Java 23: -XX:+UseZGC zaten generational; non-generational deprecated
java -XX:+UseZGC uygulama

NEREDE KOLAYLIK SAĞLAR?

Büyük bellekli, düşük gecikme gerektiren sunucu uygulamaları (örn. büyük cache, gerçek zamanlı işleme).

Geçişte ne değişti: -XX:+ZGenerational bayrağı artık gereksiz (ve deprecated). ZGC kullanan uygulamalar otomatik olarak kuşaksal modda çalışır.


9. JEP 476 — Module Import Declarations (Preview)

Detaylı anlatım için bkz. ModuleImportDeclarations.md

NEDİR?

import module M; sözdizimi ile bir modülün export ettiği tüm paketleri tek satırda içe aktarma. Örneğin import module java.base; ile java.util, java.io, java.util.stream vb. tüm paketler kullanılabilir hale gelir.

NEDEN GELDİ?

Özellikle eğitim, prototipleme ve script benzeri kullanımda onlarca import satırı yazmak yorucuydu. Modül seviyesinde toplu import bunu basitleştirir.

ESKİ vs YENİ

// ESKİ
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.time.LocalDate;

// YENİ (preview)
import module java.base;

GERÇEK HAYAT ÖRNEĞİ

Yeni başlayanların ilk programları, hızlı prototipler veya tek dosyalık çalıştırılabilir kaynak (java Ornek.java) senaryoları.

Risk: Preview. İsim çakışmaları (aynı isimli sınıflar farklı paketlerde) belirsizlik yaratabilir; bu durumda tek tek import gerekir. --enable-preview gerekir. Detaylar ModuleImportDeclarations.md dosyasında.


10. JEP 482 — Flexible Constructor Bodies (2. Preview)

NEDİR?

Bir yapıcıda (constructor) super(...) veya this(...) çağrısından önce deyimler (statement) çalıştırabilme imkânı. Önceden bu çağrı yapıcının ilk deyimi olmak zorundaydı.

NEDEN GELDİ?

super() çağrısından önce argümanları doğrulamak (validation), hazırlamak ya da normalize etmek mümkün değildi; bu yüzden statik yardımcı metotlar gibi geçici çözümler (workaround) kullanılırdı.

NE İŞE YARAR?

  • super(...)/this(...) öncesinde argüman doğrulama yapılabilir ("prologue" / ön bölge).
  • Hatalı argümanlar üst sınıf yapıcısı çalışmadan yakalanır — daha güvenli.

ESKİ vs YENİ

// ESKİ: doğrulama super'den sonra ya da statik metotla
class Kullanici extends Kisi {
    Kullanici(String ad) {
        super(ad);                 // önce zorunlu
        if (ad == null) throw ...; // doğrulama sonra (geç!)
    }
}

// YENİ (preview): super'den önce doğrulama
class Kullanici extends Kisi {
    Kullanici(String ad) {
        if (ad == null || ad.isBlank())   // önce doğrula
            throw new IllegalArgumentException("ad bos olamaz");
        super(ad);                         // sonra üst yapıcı
    }
}

Not: super(...) öncesindeki "prologue" bölgesinde henüz this örneğinin alanlarına/metotlarına erişilemez (örnek tam oluşmadığı için), sadece hesaplama/doğrulama yapılabilir.

GERÇEK HAYAT ÖRNEĞİ

Bir PositiveNumber extends Number sınıfında, üst yapıcıya değer verilmeden önce negatif değerin reddedilmesi.

Risk: Preview. --enable-preview gerekir.


11. Geçiş Notları (Java 21/22 → Java 23)

Avantajlar

  • Daha temiz dokümantasyon (Markdown Javadoc — kalıcı, risksiz).
  • ZGC kullananlar için otomatik performans iyileştirmesi (generational varsayılan).
  • Yeni dil özelliklerini (gatherers, module import, primitive patterns) deneme imkânı.

Dezavantajlar / Riskler

  • non-LTS: Yalnızca ~6 ay destek; production için 25'i (LTS) beklemek mantıklı.
  • Birçok yeni özellik preview — API değişebilir, --enable-preview zorunlu, production'da kullanımı önerilmez.
  • sun.misc.Unsafe bellek erişim metotları deprecated (JEP 471) — bunlara bağımlı eski kütüphaneler ileride sorun yaşayabilir; alternatif olarak VarHandle / MemorySegment (FFM API) önerilir.

Genel Tavsiye

  • Öğrenme/deneme: Java 23 mükemmel — yeni özellikleri deneyin.
  • Production: Stabil iş yükleri için LTS (21 veya 25) tercih edin; preview özelliklere bel bağlamayın.

12. İlgili Dosyalar

Dosyaİçerik
MarkdownJavadoc.mdMarkdown Javadoc (JEP 467) detaylı, eski/yeni yan yana
StreamGatherersGelismis.javaStream Gatherers gelişmiş, çalıştırılabilir örnekler
ModuleImportDeclarations.mdModule Import Declarations (JEP 476) detaylı

Hazırlanma tarihi: 2026 — Java 23 (Eylül 2024) baz alınmıştır.

Kod Örnekleri(1)

StreamGatherersGelismis

ortam gerekir
StreamGatherersGelismis.java
1// =============================================================================
2//  Java 23 - Stream Gatherers (Gelismis Ornekler)  -  JEP 473 (2. Preview)
3// =============================================================================
4//
5//  Stream Gatherers, Stream API'sine OZEL ARA (intermediate) operasyonlar
6//  eklemenizi saglar. Collectors'in terminal operasyonlar icin yaptigini,
7//  Gatherer ara operasyonlar icin yapar.
8//
9//  Bu dosya PREVIEW ozelligi kullanir. Derlemek ve calistirmak icin:
10//
11//    javac --release 23 --enable-preview StreamGatherersGelismis.java
12//    java  --enable-preview StreamGatherersGelismis
13//
14//  (Tek dosya modunda: java --release 23 --enable-preview StreamGatherersGelismis.java)
15//
16//  Yerlesik (built-in) gatherer fabrikalari (java.util.stream.Gatherers):
17//    - windowFixed(n)     : ardisik, ortusmeyen n'lik pencereler
18//    - windowSliding(n)   : 1 kayan, ortusen n'lik pencereler
19//    - fold(...)          : tek bir sonuca katlar (terminal benzeri)
20//    - scan(...)          : her adimda biriken (running) sonucu yayar
21//    - mapConcurrent(...) : sinirli paralellikle map
22//
23//  Ayrica Gatherer arayuzunu uygulayarak KENDI gatherer'inizi yazabilirsiniz.
24// =============================================================================
25
26import java.util.ArrayList;
27import java.util.LinkedHashSet;
28import java.util.List;
29import java.util.Set;
30import java.util.stream.Gatherer;
31import java.util.stream.Gatherers;
32import java.util.stream.Stream;
33
34public class StreamGatherersGelismis {
35
36    // -------------------------------------------------------------------------
37    //  main: tum ornekleri sirayla calistirir
38    // -------------------------------------------------------------------------
39    public static void main(String[] args) {
40        System.out.println("=== Java 23 Stream Gatherers - Gelismis Ornekler ===\n");
41
42        ornek1_hareketliOrtalama();
43        ornek2_calisanToplam_scan();
44        ornek3_sabitPencere_batchleme();
45        ornek4_ozelDedup();
46        ornek5_ardisikGruplama();
47        ornek6_limitliAlma();
48
49        System.out.println("\n=== Tum ornekler tamamlandi ===");
50    }
51
52    // =========================================================================
53    //  ORNEK 1: HAREKETLI ORTALAMA (Moving Average)  -  windowSliding
54    // -------------------------------------------------------------------------
55    //  Senaryo: Bir hissenin gunluk kapanis fiyatlari var. 3 gunluk hareketli
56    //  ortalamayi (moving average) hesaplamak istiyoruz. Bu, zaman serisi
57    //  analizinde cok yaygindir (trendi yumusatmak icin).
58    //
59    //  windowSliding(3): [g1,g2,g3], [g2,g3,g4], [g3,g4,g5] ... pencereleri verir.
60    // =========================================================================
61    static void ornek1_hareketliOrtalama() {
62        System.out.println("--- Ornek 1: 3 Gunluk Hareketli Ortalama (windowSliding) ---");
63
64        List<Double> fiyatlar = List.of(100.0, 102.0, 101.0, 105.0, 110.0, 108.0, 112.0);
65
66        List<Double> hareketliOrtalama = fiyatlar.stream()
67                // 3'luk kayan pencereler olustur: her pencere bir List<Double>
68                .gather(Gatherers.windowSliding(3))
69                // her pencerenin ortalamasini al
70                .map(pencere -> pencere.stream()
71                        .mapToDouble(Double::doubleValue)
72                        .average()
73                        .orElse(0.0))
74                .toList();
75
76        System.out.println("Fiyatlar          : " + fiyatlar);
77        System.out.println("3 gunluk ort.     : " + hareketliOrtalama);
78        System.out.println();
79    }
80
81    // =========================================================================
82    //  ORNEK 2: CALISAN TOPLAM (Running Total)  -  scan
83    // -------------------------------------------------------------------------
84    //  Senaryo: Bir banka hesabinin gunluk islem tutarlari var. Her islemden
85    //  SONRAKI bakiyeyi (kumulatif toplam) gormek istiyoruz.
86    //
87    //  scan(baslangic, biriktirici): her elemanda biriken sonucu YAYAR.
88    //  fold'dan farki: fold tek bir nihai sonuc verir, scan ara sonuclari da verir.
89    // =========================================================================
90    static void ornek2_calisanToplam_scan() {
91        System.out.println("--- Ornek 2: Calisan Toplam / Kumulatif Bakiye (scan) ---");
92
93        List<Integer> islemler = List.of(100, -30, 50, -20, 200); // + yatirma, - cekme
94
95        List<Integer> bakiyeler = islemler.stream()
96                .gather(Gatherers.scan(
97                        () -> 0,                       // baslangic bakiyesi
98                        (bakiye, islem) -> bakiye + islem)) // her adimda biriktir
99                .toList();
100
101        System.out.println("Islemler          : " + islemler);
102        System.out.println("Bakiye gecmisi    : " + bakiyeler); // [100, 70, 120, 100, 300]
103        System.out.println();
104    }
105
106    // =========================================================================
107    //  ORNEK 3: SABIT PENCERE / BATCH'LEME  -  windowFixed
108    // -------------------------------------------------------------------------
109    //  Senaryo: 1000'lerce kaydi toplu (batch) olarak isleyecegiz. Veritabanina
110    //  ya da bir API'ye tek tek degil, 4'luk gruplar halinde gondermek istiyoruz
111    //  (ag turunu / commit sayisini azaltmak icin).
112    //
113    //  windowFixed(4): [1,2,3,4], [5,6,7,8], [9,10] (son grup kismi olabilir).
114    // =========================================================================
115    static void ornek3_sabitPencere_batchleme() {
116        System.out.println("--- Ornek 3: Toplu (Batch) Isleme (windowFixed) ---");
117
118        List<Integer> kayitlar = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
119
120        List<List<Integer>> gruplar = kayitlar.stream()
121                .gather(Gatherers.windowFixed(4))
122                .toList();
123
124        System.out.println("Tum kayitlar      : " + kayitlar);
125        int no = 1;
126        for (List<Integer> grup : gruplar) {
127            System.out.println("  Batch " + (no++) + " gonderiliyor: " + grup);
128        }
129        System.out.println();
130    }
131
132    // =========================================================================
133    //  ORNEK 4: OZEL GATHERER - SIRAYI KORUYAN TEKILLESTIRME (distinct)
134    // -------------------------------------------------------------------------
135    //  Senaryo: Bir log akisindan tekrar eden kullanici ID'lerini ayikliyoruz
136    //  ama ilk gorulme sirasini korumak istiyoruz.
137    //
138    //  Burada KENDI gatherer'imizi yaziyoruz. Durumlu (stateful) bir gatherer:
139    //  daha once gorulen elemanlari bir Set'te tutar, yeni olani downstream'e iletir.
140    //
141    //  Gatherer.ofSequential(initializer, integrator):
142    //    - initializer : durum nesnesini (Set) olusturur
143    //    - integrator  : her eleman icin calisir; downstream.push(...) ile yayar
144    // =========================================================================
145    static void ornek4_ozelDedup() {
146        System.out.println("--- Ornek 4: Ozel Gatherer ile Sira Koruyan Dedup ---");
147
148        List<String> kullaniciAkisi = List.of(
149                "ali", "veli", "ali", "ayse", "veli", "ali", "fatma");
150
151        List<String> tekil = kullaniciAkisi.stream()
152                .gather(siraKoruyanTekil())
153                .toList();
154
155        System.out.println("Ham akis          : " + kullaniciAkisi);
156        System.out.println("Tekil (sirali)    : " + tekil); // [ali, veli, ayse, fatma]
157        System.out.println();
158    }
159
160    /**
161     * Ilk gorulme sirasini koruyarak tekrar edenleri eleyen ozel gatherer.
162     * (Standart .distinct() ile ayni sonucu verir ama gatherer yazimini gosterir.)
163     */
164    static <T> Gatherer<T, ?, T> siraKoruyanTekil() {
165        return Gatherer.ofSequential(
166                // 1) Durum (state): daha once gorulen elemanlar
167                LinkedHashSet::new,
168                // 2) Integrator: her eleman icin calisir
169                (Set<T> gorulenler, T eleman, Gatherer.Downstream<? super T> downstream) -> {
170                    if (gorulenler.add(eleman)) {     // ilk kez goruluyorsa true doner
171                        return downstream.push(eleman); // asagiya ilet
172                    }
173                    return true; // tekrar eden -> iletme, ama akisa devam et
174                });
175    }
176
177    // =========================================================================
178    //  ORNEK 5: OZEL GATHERER - ARDISIK ESIT ELEMANLARI GRUPLA (run-length)
179    // -------------------------------------------------------------------------
180    //  Senaryo: Bir sensorden gelen durum akisinda, ARDISIK ayni degerleri
181    //  tek bir grupta toplamak istiyoruz (run-length encoding mantigi).
182    //  Ornek: [A,A,B,B,B,A,C] -> [[A,A],[B,B,B],[A],[C]]
183    //
184    //  Bu gatherer durumludur (acik grubu tutar) ve AKIS SONUNDA kalan grubu
185    //  yaymak icin bir "finisher" kullanir.
186    // =========================================================================
187    static void ornek5_ardisikGruplama() {
188        System.out.println("--- Ornek 5: Ozel Gatherer ile Ardisik Gruplama ---");
189
190        List<String> durumlar = List.of("A", "A", "B", "B", "B", "A", "C", "C");
191
192        List<List<String>> gruplar = durumlar.stream()
193                .gather(ardisikGrupla())
194                .toList();
195
196        System.out.println("Durum akisi       : " + durumlar);
197        System.out.println("Ardisik gruplar   : " + gruplar);
198        System.out.println();
199    }
200
201    /**
202     * Ardisik (consecutive) esit elemanlari ayni gruba toplar.
203     * Durum: o anki acik grup (List). Eleman onceki ile esitse gruba ekler,
204     * degilse onceki grubu yayar ve yeni grup baslatir. Sonda kalan grup
205     * finisher ile yayilir.
206     */
207    static <T> Gatherer<T, ?, List<T>> ardisikGrupla() {
208        // Durumu tutmak icin tek elemanli bir tasiyici dizi kullaniyoruz
209        // (lambda icinde mutable referans icin pratik bir yontem).
210        return Gatherer.<T, List<List<T>>, List<T>>ofSequential(
211                // initializer: tek elemanli liste; index 0 = "o anki acik grup"
212                () -> {
213                    List<List<T>> kutu = new ArrayList<>();
214                    kutu.add(new ArrayList<>()); // acik grup
215                    return kutu;
216                },
217                // integrator
218                (kutu, eleman, downstream) -> {
219                    List<T> acikGrup = kutu.get(0);
220                    if (acikGrup.isEmpty() || acikGrup.get(0).equals(eleman)) {
221                        // ilk eleman ya da onceki ile ayni -> mevcut gruba ekle
222                        acikGrup.add(eleman);
223                    } else {
224                        // farkli -> mevcut grubu yay, yeni grup baslat
225                        boolean devam = downstream.push(List.copyOf(acikGrup));
226                        List<T> yeniGrup = new ArrayList<>();
227                        yeniGrup.add(eleman);
228                        kutu.set(0, yeniGrup);
229                        if (!devam) return false;
230                    }
231                    return true;
232                },
233                // finisher: akis bitince son acik grubu yay
234                (kutu, downstream) -> {
235                    List<T> acikGrup = kutu.get(0);
236                    if (!acikGrup.isEmpty()) {
237                        downstream.push(List.copyOf(acikGrup));
238                    }
239                });
240    }
241
242    // =========================================================================
243    //  ORNEK 6: OZEL GATHERER - KOSULA GORE ERKEN KESME (short-circuit)
244    // -------------------------------------------------------------------------
245    //  Senaryo: Bir sensorden gelen sonsuz/uzun bir akista, toplam belirli bir
246    //  esigi gecene KADAR elemanlari al, esik asilinca akisi DURDUR.
247    //
248    //  Gatherer integrator'unde downstream.push(...) FALSE dondurursek ya da
249    //  biz false dondururek akisi kisa devre (short-circuit) yaptirabiliriz.
250    //  Bu, sonsuz Stream'lerle bile guvenli calisir.
251    // =========================================================================
252    static void ornek6_limitliAlma() {
253        System.out.println("--- Ornek 6: Ozel Gatherer ile Toplam Esigine Kadar Al ---");
254
255        // Sonsuz bir akis: 1, 2, 3, 4, ... (sadece esik mantigini test icin)
256        List<Integer> alinanlar = Stream.iterate(1, n -> n + 1)
257                .gather(toplamEsigineKadar(15)) // toplam 15'i gecince dur
258                .toList();
259
260        System.out.println("Toplam <= 15 olana kadar alinanlar: " + alinanlar);
261        // 1+2+3+4+5 = 15 -> 5'e kadar alir, 6 eklenince 21>15 olur, durur.
262        System.out.println();
263    }
264
265    /**
266     * Elemanlari toplaya toplaya esige kadar alir; esik asildiginda akisi keser.
267     * Durum: bir int dizisi [toplam] (mutable sayac).
268     */
269    static Gatherer<Integer, ?, Integer> toplamEsigineKadar(int esik) {
270        return Gatherer.ofSequential(
271                () -> new int[]{0}, // durum: toplam
272                (toplam, eleman, downstream) -> {
273                    if (toplam[0] + eleman > esik) {
274                        return false; // esik asildi -> akisi durdur (short-circuit)
275                    }
276                    toplam[0] += eleman;
277                    return downstream.push(eleman);
278                });
279    }
280}
canlı çalıştırma için ortam gerekir

Bu sürüm-analizi örneği tek dosya olarak yerel JDK 21 ile çalıştırılamadı (bağımsız bir main içermeyebilir ya da daha yeni bir JDK / önizleme özelliği gerektirir). İlgili dil özelliği yukarıdaki anlatım ve kodda gösterilmiştir.