Eğitim Portalı/Sürüm Analizi/Java 15 (Eylül 2020) — Detaylı Sürüm Analizi
Sürüm Analizi05-java-versiyon-analizi/08-Java-15

Java 15 (Eylül 2020) — Detaylı Sürüm Analizi

> Bu doküman Java 15 sürümünü Türkçe olarak, her özellik için > NEDİR / NEDEN GELDİ / NE İŞE YARAR / NEREDE KOLAYLIK SAĞLAR / > ESKİ vs YENİ / GERÇEK HAYAT ÖRNEĞİ başlıklarıyla derinlemesine açıklar.

Java 15 (Eylül 2020) — Detaylı Sürüm Analizi

Bu doküman Java 15 sürümünü Türkçe olarak, her özellik için NEDİR / NEDEN GELDİ / NE İŞE YARAR / NEREDE KOLAYLIK SAĞLAR / ESKİ vs YENİ / GERÇEK HAYAT ÖRNEĞİ başlıklarıyla derinlemesine açıklar.


0. Sürüm Bilgisi (Genel Bakış)

BilgiDeğer
SürümJava SE 15 / JDK 15
Çıkış TarihiEylül 2020
Destek TürüNon-LTS (kısa destekli ara sürüm)
Bir ÖncekiJava 14 (Mart 2020)
Bir SonrakiJava 16 (Mart 2021)

Java 15, "feature release" (özellik sürümü) modelinin parçası olan 6 aylık ara sürümlerden biridir. LTS (uzun süreli destek) değildir — LTS sürümleri 8, 11, 17, 21'dir. Buna rağmen Java 15, dil tarihinin çok önemli bir kavşağıdır: Text Blocks burada kalıcı oldu ve Sealed Classes burada ilk kez göründü.

Özelliklerin Olgunluk (Preview → Kalıcı) Durumu

ÖzellikJava 13Java 14Java 15Java 16Java 17
Text BlocksPreviewPreviewKALICI ✅KalıcıKalıcı
Records1. Preview2. PreviewKalıcı ✅Kalıcı
Pattern Matching for instanceof1. Preview2. PreviewKalıcı ✅Kalıcı
Sealed Classes1. Preview ⭐2. PreviewKalıcı ✅

Önemli ayrım:

  • Text Blocks: 13–14'te preview idi, 15'te KALICI oldu → artık --enable-preview GEREKMEZ.
  • Sealed Classes: 15'te İLK kez (preview) geldi → 15/16'da --enable-preview gerekir, 17'de kalıcı.
  • Records: 15'te İKİNCİ preview → 16'da kalıcı.
  • Pattern Matching for instanceof: 15'te İKİNCİ preview → 16'da kalıcı.

Bu Repodaki Çalışan Örnek Dosyalar

DosyaKonu
TextBlocksKalici.javaText Blocks (kalıcı) — JSON/SQL/HTML örnekleri
SealedClassesPreview.javaSealed Classes — Şekil ve Ödeme hiyerarşileri
RecordSealedBirlikte.javaRecords + Sealed birlikte — Cebirsel veri tipleri (ADT)

1. Text Blocks — ARTIK KALICI (Standart) ✅

İlgili dosya: TextBlocksKalici.java

NEDİR

Üç çift tırnakla (""") başlayıp biten çok satırlı string yazma biçimidir. JEP 378 ile Java 15'te kalıcılaştı.

NEDEN GELDİ (Hangi problemi çözüyor)

Java'da çok satırlı metin yazmak tarihsel olarak acı vericiydi:

  • Her satır sonuna elle \n eklemek,
  • Satırları + ile birleştirmek,
  • Metnin içindeki her " karakterini \" olarak kaçırmak (özellikle JSON/HTML'de).

Buna topluca "kaçış karakteri cehennemi" denirdi. Kod, içerdiği metnin gerçek halini hiç andırmazdı; okunması ve bakımı zordu.

NE İŞE YARAR

Metni, bir editöre yazar gibi doğal, hizalı ve kaçışsız yazmamızı sağlar. Satır sonları otomatik eklenir, gereksiz girinti otomatik temizlenir.

NEREDE KOLAYLIK SAĞLAR

  • JSON gövdeleri (REST istek/yanıt örnekleri, test verileri)
  • SQL sorguları (çok satırlı, hizalı, okunaklı)
  • HTML / XML şablonları (e-posta, rapor)
  • JSON Schema, GraphQL, regex açıklamaları, çok satırlı loglar

ESKİ vs YENİ

// ESKİ (kaçış cehennemi)
String json = "{\n" +
              "  \"ad\": \"Ahmet\",\n" +
              "  \"yas\": 30\n" +
              "}";

// YENİ (Java 15 — kalıcı Text Block)
String json = """
        {
          "ad": "Ahmet",
          "yas": 30
        }""";

Özel davranışlar (dosyada örnekli)

  • Otomatik girinti temizliği (incidental white space): en az girintili satıra ve kapanış """ konumuna göre soldaki ortak boşluk silinir.
  • \ (satır sonunda): bir sonraki satırla birleştirir (yeni satır eklemez).
  • \s: tek bir boşluk; satır sonundaki boşlukların korunmasını sağlar.
  • Java 15 ayrıca String.formatted(...), String.stripIndent(), String.translateEscapes() metotlarını da getirdi.

GERÇEK HAYAT ÖRNEĞİ

Bir e-ticaret uygulamasında sipariş onayı HTML e-postası üretmek. Eskiden HTML şablonu Java string olarak yazmak imkansıza yakındı; Text Block + formatted ile şablon, gerçek bir .html dosyası gibi okunur (bkz. ornek5_GercekHayat).


2. Sealed Classes (Mühürlü Sınıflar) — İLK PREVIEW ⭐

İlgili dosya: SealedClassesPreview.java

Sürüm: Java 15'te ilk preview (JEP 360). 15/16'da derlemek için javac --release 15 --enable-preview ... gerekir. Java 17'de kalıcı oldu; 17+ ile bayraksız derlenir. (Dosyamız 17+ uyumlu yazıldı.)

NEDİR

Bir sınıf/arayüzün kendisini kimin extend/implement edebileceğini açıkça sınırlandırmasıdır. Bu kontrol sealed ve permits anahtar kelimeleriyle yapılır.

Dört anahtar kelime:

  • sealed: "Mühürlüyüm; sadece izin verdiklerim alt tip olabilir."
  • permits: İzin verilen alt tiplerin listesi.
  • final: Alt tip kapatılır; ondan kimse türeyemez.
  • non-sealed: Alt tip mührü açar; ondan herkes türeyebilir.

Kural: Mühürlü bir tipi extend eden her alt sınıf final, sealed veya non-sealed üçünden birini seçmek zorundadır.

NEDEN GELDİ (Hangi problemi çözüyor)

Eskiden bir hiyerarşiyi "yalnızca şu sınıflar genişletebilsin" diye kapatmanın temiz bir yolu yoktu:

  • final → hiç kimse türetemez (çok katı).
  • package-private constructor hilesi → sadece aynı pakette türetilebilir; ama gerçek bir kontrol değil, niyeti belirsiz ve API tüketicisini şaşırtır.
  • Hiçbir yöntem derleyiciye "bu hiyerarşi KAPALI ve alt tipler şunlardır" bilgisini veremiyordu → exhaustive (tüm durumları kapsayan) switch yapılamıyordu.

NE İŞE YARAR

  • Alan modelini (domain) kasıtlı ve bilinen bir küme olarak modellemek.
  • Derleyicinin tüm alt tipleri bilmesi sayesinde eksiksiz switch yazmak; yeni bir alt tip eklenip de bir switch güncellenmezse derleme hatası almak.

NEREDE KOLAYLIK SAĞLAR

  • API kütüphaneleri: dışarıdan istenmeyen alt tip türetilmesini engelleme.
  • Durum makineleri, AST/ifade ağaçları, ödeme/komut/olay türleri gibi sonlu seçenek kümeleri.

ESKİ vs YENİ

// ESKİ — niyet belirsiz, gerçek kontrol yok
abstract class Sekil {
    Sekil() {}              // package-private "hile" constructor
}

// YENİ — net ve derleyici denetimli
sealed abstract class Sekil permits Daire, Kare, Ucgen {}
final class Daire extends Sekil { ... }       // kapalı
final class Kare  extends Sekil { ... }       // kapalı
non-sealed class Ucgen extends Sekil { ... }  // bu dal kasıtlı açık

GERÇEK HAYAT ÖRNEĞİ

Ödeme türü hiyerarşisi. Bir ödeme sisteminde yöntemler sabit ve bilinen bir küme olmalıdır: KrediKarti, Havale, Nakit. Üçüncü partinin sisteme "BitcoinÖdeme" gibi keyfi bir tür eklemesi iş kuralları açısından tehlikelidir. sealed interface Odeme permits ... ile bu küme mühürlenir; islemUcreti() içindeki switch her durumu kapsamak zorunda kalır (bkz. odemeDemosu).


3. Records — İKİNCİ PREVIEW

İlgili dosya (kullanım): RecordSealedBirlikte.java

Sürüm: Java 14'te ilk preview, Java 15'te ikinci preview (JEP 384), Java 16'da kalıcı.

NEDİR

Yalnızca veri taşıyan, değişmez (immutable) sınıfları tek satırda tanımlamayı sağlayan tip: record Nokta(int x, int y) {}. Derleyici otomatik olarak constructor, alan erişimcileri, equals, hashCode ve toString üretir.

NEDEN GELDİ

Klasik "POJO/DTO" sınıfları onlarca satırlık tekrarlı kalıp kod (boilerplate) gerektiriyordu: özel alanlar, getter'lar, equals/hashCode/toString. Bu kod hem yorucuydu hem de hataya açıktı.

NE İŞE YARAR / NEREDE KOLAYLIK SAĞLAR

  • DTO'lar, API yanıt/istek modelleri, değer nesneleri (value objects).
  • Çoklu dönüş değerleri, harita anahtarları, kayıt ağaçları.
  • 15'teki ikinci preview ile yerel (local) record'lar ve anotasyon davranışı iyileştirildi.

ESKİ vs YENİ

// ESKİ — ~30 satır boilerplate
public final class Nokta {
    private final int x, y;
    public Nokta(int x, int y){ this.x=x; this.y=y; }
    public int x(){ return x; } public int y(){ return y; }
    // equals, hashCode, toString ...
}

// YENİ
record Nokta(int x, int y) {}

GERÇEK HAYAT ÖRNEĞİ

İfade ağacındaki her düğüm (Sayi, Toplama, Carpma) tek satırlık record'tur; immutable ve eşitliği otomatik (bkz. bölüm 5 ve RecordSealedBirlikte.java).


4. Pattern Matching for instanceof — İKİNCİ PREVIEW

İlgili kullanım: SealedClassesPreview.java içindeki aciklama(...) metodu.

Sürüm: Java 14'te ilk preview, Java 15'te ikinci preview (JEP 375), Java 16'da kalıcı.

NEDİR

instanceof kontrolünün, başarılı olduğunda otomatik olarak doğru tipte bir değişken bağlamasıdır (binding variable).

NEDEN GELDİ

Klasik instanceof her zaman ardından manuel cast gerektiriyordu: önce tip kontrol et, sonra aynı tipe elle dönüştür. Bu, tekrarlı ve hataya açıktı.

ESKİ vs YENİ

// ESKİ
if (obj instanceof String) {
    String s = (String) obj;   // manuel cast
    System.out.println(s.length());
}

// YENİ
if (obj instanceof String s) {  // s otomatik bağlanır
    System.out.println(s.length());
}

NEREDE KOLAYLIK SAĞLAR

equals implementasyonları, tip ayrıştırma zincirleri ve (ileride) sealed + record ile birleşince eksiksiz pattern matching switch'in temelini oluşturur.


5. Records + Sealed Birlikte — Cebirsel Veri Tipleri (ADT)

İlgili dosya: RecordSealedBirlikte.java

Sürüm notu: Java 15'te hem records (2. preview) hem sealed (1. preview) preview idi. O dönemde --release 15 --enable-preview gerekiyordu. Java 17+ ile (sealed kalıcı, records kalıcı) bayraksız derlenir.

NEDİR / NEDEN GÜÇLÜ

Fonksiyonel dillerden gelen Cebirsel Veri Tipleri desenini Java'ya getirir:

  • sealed interface"bu tip şu seçeneklerden BİRİDİR" (sum / toplam tip)
  • record"her seçenek şu alanlardan oluşur" (product / çarpım tip)
  • pattern matching switch → her seçeneği güvenle ayrıştırma
sealed interface Sekil permits Daire, Dikdortgen, Ucgen {}
record Daire(double yaricap) implements Sekil {}
record Dikdortgen(double en, double boy) implements Sekil {}
record Ucgen(double taban, double yukseklik) implements Sekil {}

static double alan(Sekil s) {
    return switch (s) {                 // 'default' GEREKMEZ — sealed sayesinde
        case Daire d        -> Math.PI * d.yaricap() * d.yaricap();
        case Dikdortgen d   -> d.en() * d.boy();
        case Ucgen u        -> u.taban() * u.yukseklik() / 2.0;
    };
}

Üçlünün birleşimi modern Java'da şunu sağlar: az kod, değişmezlik, ve en önemlisi derleyici garantili eksiksizlik. Yeni bir alt tip eklenip de switch güncellenmezse derleme başarısız olur — yani "unutulan durum" hatası daha çalıştırmadan, derleme aşamasında yakalanır.

GERÇEK HAYAT ÖRNEĞİ

İfade ağacı (Expression Tree) / hesap makinesi. Bir matematik ifadesi ya bir sayıdır, ya iki ifadenin toplamı, ya çarpımıdır. (2 + 3) * 4 ifadesi Carpma(Toplama(Sayi(2), Sayi(3)), Sayi(4)) olarak modellenir ve yinelemeli hesapla(...) ile değerlendirilir (bkz. ifadeAgaciDemosu).


6. Sadece README'de Açıklanan Diğer Özellikler

6.1 Hidden Classes (Gizli Sınıflar) — JEP 371

  • NEDİR: Çalışma zamanında (runtime) üretilen, normal sınıf yükleyiciler tarafından bulunamayan/keşfedilemeyen sınıflardır. Bytecode'dan dinamik olarak oluşturulur, başka kodlardan doğrudan referans verilemez.
  • NEDEN GELDİ: Spring, Hibernate, mockito, dinamik proxy/lambda altyapıları gibi framework'ler çalışma zamanında sürekli sınıf üretir. Eski yöntem (Unsafe.defineAnonymousClass) desteklenmeyen bir iç API idi.
  • NE İŞE YARAR: Framework'lere, çöp toplayıcı (GC) tarafından gerektiğinde boşaltılabilen, izole ve güvenli sınıf üretme imkanı verir. Bellek sızıntısı ve sınıf yükleyici şişmesini azaltır.
  • NOT: Bu, son kullanıcı geliştirici için değil, çerçeve (framework) yazarları için bir altyapı özelliğidir; bu repoda kod örneği yoktur.

6.2 ZGC ve Shenandoah — ÜRETİME HAZIR (Production-Ready)

  • ZGC (JEP 377): Düşük gecikmeli (çok kısa duraklamalı), terabaytlara kadar ölçeklenen çöp toplayıcı. Java 11'de deneysel gelmişti; Java 15'te üretime hazır ilan edildi.
  • Shenandoah (JEP 379): Yine düşük duraklamalı GC; Java 12'de deneysel, Java 15'te üretime hazır.
  • NE İŞE YARAR: GC duraklamalarının (pause) milisaniyeler değil mikrosaniyeler seviyesine inmesi gereken, büyük heap'li, gecikmeye duyarlı uygulamalarda (ticaret platformları, oyun sunucuları, büyük önbellekler).
  • Kullanım: java -XX:+UseZGC ... veya java -XX:+UseShenandoahGC ... (artık -XX:+UnlockExperimentalVMOptions gerekmez).

6.3 Nashorn JavaScript Engine — KALDIRILDI (JEP 372) ⚠️

  • NEDİR: Nashorn, JVM içinde JavaScript çalıştıran motordu (Java 8'de gelmiş, Java 11'de "deprecated" işaretlenmişti). Java 15'te tamamen kaldırıldı.
  • NEDEN: ECMAScript'in hızlı evrimine ayak uydurmak maliyetliydi; ekosistemde GraalVM JavaScript gibi daha güçlü alternatifler oluştu.
  • MİGRASYON RİSKİ ⚠️: jdk.nashorn.* paketlerini veya ScriptEngineManager ile "nashorn" motorunu kullanan kodlar Java 15'te ÇALIŞMAZ. jjs komut satırı aracı da kaldırıldı. Bu tür kodların GraalVM JavaScript veya başka bir script motoruna taşınması gerekir. Java 11'den 15'e geçişte en önemli "bozucu değişiklik"lerden (breaking change) biridir.

7. Java 14'ten Java 15'e Geçişte Ne Değişti?

KonuJava 14Java 15
Text BlocksPreview (bayrak gerekli)Kalıcı (bayraksız) ✅
Records1. Preview2. Preview (iyileştirildi)
Pattern Matching instanceof1. Preview2. Preview
Sealed ClassesYok1. Preview (yeni!)
ZGC / ShenandoahDeneyselÜretime hazır
NashornDeprecatedKaldırıldı ⚠️
Yeni String metotlarıformatted, stripIndent, translateEscapes

8. Avantajlar / Dezavantajlar / Riskler

✅ Avantajlar

  • Text Blocks artık kalıcı: JSON/SQL/HTML içeren kod radikal biçimde okunaklı.
  • Sealed Classes (ön izleme): Alan modelini güvenle kapatma; modern ADT'ye kapı.
  • Records + Sealed + Pattern Matching birlikte: az kodla, derleyici garantili, eksiksiz (exhaustive) modelleme — Java'yı fonksiyonel dillere yaklaştırır.
  • ZGC/Shenandoah üretime hazır: Büyük, gecikmeye duyarlı sistemler için ciddi performans seçeneği.

⚠️ Dezavantajlar

  • Java 15 LTS değildir. Üretim için genelde 11 veya 17 (LTS) tercih edilir; 15 daha çok yeni özellikleri erken denemek içindir.
  • Sealed/Records/Pattern Matching bu sürümde preview — API/sözdizimi sonraki sürümlerde değişebilir; preview kodu üretime almak risklidir.
  • Preview özellikleri --enable-preview bayrağı ve sürüm kilidi ile gelir; derlenen sınıflar yalnızca aynı JDK sürümünde çalışır.

🚨 Riskler (özellikle migrasyonda)

  • Nashorn kaldırıldı: jdk.nashorn.*, "nashorn" script motoru veya jjs kullanan kodlar bozulur. Geçiş öncesi bağımlılık taraması şart.
  • Preview özellik kullanan kod, bir sonraki sürüme (16/17) geçerken sözdizimi/API değişiklikleri nedeniyle güncelleme gerektirebilir.
  • 15 non-LTS olduğu için güvenlik güncellemeleri kısa sürelidir; uzun ömürlü projelerde doğrudan 15'e bağlanmak önerilmez.

9. Derleme ve Çalıştırma

# Text Blocks — Java 15+ (kalıcı, bayraksız)
javac TextBlocksKalici.java
java  TextBlocksKalici

# Sealed Classes — Java 15/16'da preview bayrağı gerekir:
#   javac --release 15 --enable-preview SealedClassesPreview.java
#   java  --enable-preview SealedClassesPreview
# Java 17+ ile bayraksız:
javac SealedClassesPreview.java
java  SealedClassesPreview

# Records + Sealed — Java 15'te ikisi de preview idi:
#   javac --release 15 --enable-preview RecordSealedBirlikte.java
#   java  --enable-preview RecordSealedBirlikte
# Java 17+ ile bayraksız (switch pattern matching için Java 21+ önerilir):
javac RecordSealedBirlikte.java
java  RecordSealedBirlikte

Bu repodaki .java dosyaları, modern bir JDK (17+, tercihen 21) ile preview bayrağı olmadan derlenip çalıştırılabilecek şekilde yazılmış ve test edilmiştir.

Kod Örnekleri(3)

RecordSealedBirlikte

çalıştırılabilir
RecordSealedBirlikte.java
1// =====================================================================================
2//  RecordSealedBirlikte.java
3//  Java 15 - Records + Sealed Classes BİRLİKTE (Cebirsel Veri Tipleri / ADT)
4// =====================================================================================
5//
6//  ÖNEMLİ SÜRÜM NOTU:
7//  ------------------
8//  - Records:        Java 14'te ilk preview, Java 15'te İKİNCİ preview (JEP 384),
9//                    Java 16'da KALICI (JEP 395).
10//  - Sealed Classes: Java 15'te İLK preview (JEP 360), Java 17'de KALICI (JEP 409).
11//
12//  Yani Java 15'te HEM records HEM sealed classes PREVIEW durumundaydı. O zaman
13//  bu dosyayı derlemek için şu gerekiyordu:
14//        javac --release 15 --enable-preview RecordSealedBirlikte.java
15//        java  --enable-preview RecordSealedBirlikte
16//
17//  Java 17 ve sonrasında her ikisi de kalıcı olduğu için HİÇBİR BAYRAK gerekmez:
18//        javac RecordSealedBirlikte.java
19//        java  RecordSealedBirlikte
20//
21//  Bu dosya Java 17+ (ve switch pattern matching için Java 21+) ile derlenecek
22//  şekilde yazılmıştır.
23//
24// =====================================================================================
25//
26//  CEBİRSEL VERİ TİPLERİ (Algebraic Data Types - ADT) NEDİR?
27//  ---------------------------------------------------------
28//  Fonksiyonel dillerden (Haskell, Scala, Kotlin) gelen bir kavram. Bir tip,
29//  "şunlardan BİRİ olabilir" şeklinde KAPALI bir seçenek kümesiyle tanımlanır:
30//
31//      sealed interface  -> "bu tip şu seçeneklerden biridir" (toplam / sum type)
32//      record            -> "her seçenek şu alanlardan oluşur" (çarpım / product type)
33//
34//  Bu ikilinin Java 15 ile birleşmesi, Java'ya MODERN, GÜVENLİ ve KISA bir
35//  modelleme aracı kazandırdı:
36//    - sealed -> derleyici tüm olasılıkları bilir (exhaustive switch).
37//    - record -> az kodla değişmez (immutable) veri taşıyıcıları.
38//    - pattern matching -> her seçeneği tek tek, güvenle ayrıştırma.
39//
40// =====================================================================================
41
42public class RecordSealedBirlikte {
43
44    public static void main(String[] args) {
45        System.out.println("===== JAVA 15: RECORDS + SEALED (ADT) DEMOSU =====\n");
46        geometriDemosu();
47        ifadeAgaciDemosu();
48    }
49
50    // =====================================================================================
51    //  ÖRNEK 1: GEOMETRİK ŞEKİLLER - sealed interface + record'lar
52    // =====================================================================================
53    //
54    //  "Sekil" mühürlü bir arayüzdür ve SADECE Daire, Dikdortgen, Ucgen olabilir.
55    //  Her seçenek bir record'dur: kısa, değişmez ve otomatik equals/hashCode/toString'li.
56    //
57    //  Bu klasik ADT desenidir:
58    //      sealed interface Sekil permits Daire, Dikdortgen, Ucgen
59    //      record Daire(double yaricap) implements Sekil
60    //      ...
61    //
62    // =====================================================================================
63
64    sealed interface Sekil permits Daire, Dikdortgen, Ucgen {}
65
66    record Daire(double yaricap) implements Sekil {}
67    record Dikdortgen(double en, double boy) implements Sekil {}
68    record Ucgen(double taban, double yukseklik) implements Sekil {}
69
70    // Alan hesabı: sealed + record + switch pattern matching birleşimi.
71    // 'Sekil' mühürlü olduğu için switch TÜM olasılıkları kapsar; 'default' GEREKMEZ.
72    // Eğer yarın 'permits' listesine yeni bir şekil eklenirse ama bu switch
73    // güncellenmezse, DERLEYİCİ HATA verir. İşte ADT'nin güvenlik gücü budur.
74    static double alan(Sekil s) {
75        return switch (s) {
76            // 'Record deconstruction' (kayıt ayrıştırma): record'un alanlarını
77            // doğrudan değişkenlere açıyoruz. (Java 21 record pattern özelliği.)
78            case Daire(double r)             -> Math.PI * r * r;
79            case Dikdortgen(double en, double boy) -> en * boy;
80            case Ucgen(double t, double h)   -> (t * h) / 2.0;
81        };
82    }
83
84    static void geometriDemosu() {
85        System.out.println("----- ÖRNEK 1: GEOMETRİK ŞEKİLLER (ADT) -----");
86
87        Sekil[] sekiller = {
88                new Daire(3),
89                new Dikdortgen(4, 5),
90                new Ucgen(6, 2)
91        };
92
93        for (Sekil s : sekiller) {
94            // record'ların otomatik toString'i sayesinde nesneler okunaklı yazılır.
95            System.out.printf("%-30s alan = %.2f%n", s, alan(s));
96        }
97        System.out.println();
98        System.out.println("Not: 'switch' içinde 'default' yok çünkü 'Sekil' mühürlü");
99        System.out.println("ve derleyici tüm alt tiplerin kapsandığını biliyor.");
100        System.out.println();
101    }
102
103
104    // =====================================================================================
105    //  ÖRNEK 2: GERÇEK HAYAT - İFADE AĞACI (Expression Tree) ile HESAP MAKİNESİ
106    // =====================================================================================
107    //
108    //  ADT'nin en klasik ve güçlü örneği: bir matematik ifadesini ağaç olarak modellemek.
109    //  Bir "İfade" ya bir sayıdır, ya iki ifadenin toplamıdır, ya da çarpımıdır.
110    //  Bu YİNELEMELİ (recursive) yapıyı sealed + record ile çok temiz ifade ederiz:
111    //
112    //      sealed interface Ifade permits Sayi, Toplama, Carpma
113    //      record Sayi(double deger)             implements Ifade
114    //      record Toplama(Ifade sol, Ifade sag)  implements Ifade
115    //      record Carpma(Ifade sol, Ifade sag)   implements Ifade
116    //
117    //  Örnek ifade:  (2 + 3) * 4   ->  Carpma(Toplama(Sayi(2), Sayi(3)), Sayi(4))
118    //
119    // =====================================================================================
120
121    sealed interface Ifade permits Sayi, Toplama, Carpma {}
122
123    record Sayi(double deger) implements Ifade {}
124    record Toplama(Ifade sol, Ifade sag) implements Ifade {}
125    record Carpma(Ifade sol, Ifade sag) implements Ifade {}
126
127    // İfadeyi yineleyerek (recursive) hesaplar. Her durumda record alanları ayrıştırılır.
128    static double hesapla(Ifade ifade) {
129        return switch (ifade) {
130            case Sayi(double d)            -> d;
131            case Toplama(Ifade sol, Ifade sag) -> hesapla(sol) + hesapla(sag);
132            case Carpma(Ifade sol, Ifade sag)  -> hesapla(sol) * hesapla(sag);
133        };
134    }
135
136    // İfadeyi insan-okur metne çevirir (parantezli). Yine sealed + pattern matching gücü.
137    static String yazdir(Ifade ifade) {
138        return switch (ifade) {
139            case Sayi(double d)            -> (d == Math.floor(d)) ? String.valueOf((long) d) : String.valueOf(d);
140            case Toplama(Ifade sol, Ifade sag) -> "(" + yazdir(sol) + " + " + yazdir(sag) + ")";
141            case Carpma(Ifade sol, Ifade sag)  -> "(" + yazdir(sol) + " * " + yazdir(sag) + ")";
142        };
143    }
144
145    static void ifadeAgaciDemosu() {
146        System.out.println("----- ÖRNEK 2: GERÇEK HAYAT - İFADE AĞACI -----");
147
148        // (2 + 3) * 4  =  20
149        Ifade ifade1 = new Carpma(
150                new Toplama(new Sayi(2), new Sayi(3)),
151                new Sayi(4)
152        );
153
154        // 10 + (5 * 6)  =  40
155        Ifade ifade2 = new Toplama(
156                new Sayi(10),
157                new Carpma(new Sayi(5), new Sayi(6))
158        );
159
160        System.out.println(yazdir(ifade1) + " = " + hesapla(ifade1));
161        System.out.println(yazdir(ifade2) + " = " + hesapla(ifade2));
162
163        System.out.println();
164        System.out.println("NEDEN GÜÇLÜ?");
165        System.out.println("- sealed: 'Ifade' sadece Sayi/Toplama/Carpma olabilir (kapalı küme).");
166        System.out.println("- record: Her düğüm 1 satırda; immutable, eşitlik otomatik.");
167        System.out.println("- switch pattern matching: Her durumu güvenle, default'suz işleriz.");
168        System.out.println("- Yeni bir işlem (örn. Cikarma) eklenince derleyici tüm switch'leri");
169        System.out.println("  güncellememizi ZORUNLU kılar -> unutulan durum = derleme hatası.");
170        System.out.println("\n===== DEMO SONU =====");
171    }
172}
Çıktı yerel JDK 21 ile yakalandı — tarayıcıda JVM çalışmaz.

SealedClassesPreview

çalıştırılabilir
SealedClassesPreview.java
1// =====================================================================================
2//  SealedClassesPreview.java
3//  Java 15 - Sealed Classes (Mühürlü Sınıflar) - İLK PREVIEW (Önizleme)
4// =====================================================================================
5//
6//  ÖNEMLİ SÜRÜM NOTU:
7//  ------------------
8//  - Sealed Classes ilk olarak Java 15'te (Eylül 2020, JEP 360) PREVIEW geldi.
9//  - Java 16'da ikinci preview oldu (JEP 397).
10//  - Java 17'de (Eylül 2021, JEP 409) KALICI / STANDART hale geldi.
11//
12//  DERLEME:
13//  - Java 15 veya 16'da derlerken/çalıştırırken preview bayrakları GEREKİR:
14//        javac --release 15 --enable-preview SealedClassesPreview.java
15//        java  --enable-preview SealedClassesPreview
16//  - Java 17 ve sonrasında HİÇBİR ÖZEL BAYRAK GEREKMEZ (kalıcı oldu):
17//        javac SealedClassesPreview.java
18//        java  SealedClassesPreview
19//
20//  Bu dosya Java 17+ ile preview bayrağı OLMADAN derlenebilecek şekilde yazılmıştır.
21//
22// =====================================================================================
23
24public class SealedClassesPreview {
25
26    public static void main(String[] args) {
27        System.out.println("===== JAVA 15 SEALED CLASSES (PREVIEW) DEMOSU =====\n");
28
29        sekilDemosu();
30        odemeDemosu();
31    }
32
33    // =====================================================================================
34    //  SEALED CLASSES NEDİR? (Kavramsal açıklama)
35    // =====================================================================================
36    //
37    //  "sealed" (mühürlü) bir sınıf veya arayüz, KENDİSİNİ KİMİN extend/implement
38    //  EDEBİLECEĞİNİ açıkça belirler. Bunu "permits" anahtar kelimesiyle yapar.
39    //
40    //  Anahtar kelimeler:
41    //  - sealed     : "Bu sınıf/arayüz mühürlüdür, sadece izin verdiklerim alt tip olabilir."
42    //  - permits    : "İzin verilen alt tipler şunlardır: ..."
43    //  - final      : Alt tip kapatılır, ondan da kimse türeyemez. (Hiyerarşi burada biter.)
44    //  - non-sealed : Alt tip mührü AÇAR, artık herkes ondan türeyebilir. (Kontrolü gevşetir.)
45    //
46    //  Bir mühürlü sınıfı extend eden HER alt sınıf, şu üçünden BİRİNİ seçmek ZORUNDADIR:
47    //  final, sealed veya non-sealed.
48    //
49    // =====================================================================================
50
51    // -------------------------------------------------------------------------------------
52    //  ESKİ DÜNYA (Java 14 ve öncesi) - Bir hiyerarşiyi kapatmak NEDEN zordu?
53    // -------------------------------------------------------------------------------------
54    //  Bir Shape (Şekil) sınıfının SADECE Circle, Square, Triangle tarafından
55    //  extend edilmesini istediğimizi düşünelim. Eski Java'da bunun temiz bir yolu YOKTU:
56    //
57    //  YÖNTEM A) final yapmak -> Hiç kimse extend edemez. Ama biz BAZILARINA izin
58    //            vermek istiyoruz; bu çok katı.
59    //
60    //  YÖNTEM B) package-private constructor (görünür olmayan kurucu) hilesi ->
61    //            Sınıfı sadece aynı pakette türetilebilir kılarsın. Ama:
62    //              * Farklı paketten geliştirici "Neden extend edemiyorum?" diye şaşırır.
63    //              * Aynı pakete sınıf ekleyen herkes yine de türetebilir (gerçek kontrol yok).
64    //              * Derleyici "exhaustive" (tüm durumları kapsayan) switch yapamaz.
65    //
66    //  YÖNTEM C) private constructor + iç içe statik sınıflar -> Çok hantal, esnek değil.
67    //
68    //  SONUÇ: Eski yöntemlerin hiçbiri derleyiciye "bu hiyerarşi KAPALI ve alt tipler
69    //  şunlardır" bilgisini veremiyordu. Sealed Classes tam olarak bunu çözer.
70    // -------------------------------------------------------------------------------------
71
72
73    // =====================================================================================
74    //  ÖRNEK 1: Şekil (Sekil) Hiyerarşisi - SADECE Daire, Kare, Ucgen izinli
75    // =====================================================================================
76
77    // "sealed" + "permits": Sekil sadece bu üç sınıf tarafından genişletilebilir.
78    // Başka bir sınıf "extends Sekil" yazmaya çalışırsa DERLEME HATASI alır.
79    sealed static abstract class Sekil
80            permits Daire, Kare, Ucgen {
81        abstract double alan();
82    }
83
84    // "final": Daire hiyerarşinin yaprağıdır, ondan kimse türeyemez.
85    static final class Daire extends Sekil {
86        final double yaricap;
87        Daire(double yaricap) { this.yaricap = yaricap; }
88        double alan() { return Math.PI * yaricap * yaricap; }
89    }
90
91    // "final": Kare de kapalıdır.
92    static final class Kare extends Sekil {
93        final double kenar;
94        Kare(double kenar) { this.kenar = kenar; }
95        double alan() { return kenar * kenar; }
96    }
97
98    // "non-sealed": Ucgen mührü AÇAR. Yani isteyen herkes Ucgen'den türeyebilir
99    // (örn. Eskenar, Ikizkenar gibi). Bu, hiyerarşinin bir dalını kasıtlı açmak içindir.
100    static non-sealed class Ucgen extends Sekil {
101        final double taban, yukseklik;
102        Ucgen(double taban, double yukseklik) { this.taban = taban; this.yukseklik = yukseklik; }
103        double alan() { return (taban * yukseklik) / 2.0; }
104    }
105
106    // non-sealed olduğu için Ucgen'den serbestçe türetebiliyoruz:
107    static class Eskenar extends Ucgen {
108        Eskenar(double kenar) { super(kenar, kenar * Math.sqrt(3) / 2.0); }
109    }
110
111    static void sekilDemosu() {
112        System.out.println("----- ÖRNEK 1: ŞEKİL HİYERARŞİSİ -----");
113
114        Sekil[] sekiller = {
115                new Daire(5),
116                new Kare(4),
117                new Ucgen(6, 3),
118                new Eskenar(2)
119        };
120
121        for (Sekil s : sekiller) {
122            System.out.printf("%-10s alan = %.2f%n", aciklama(s), s.alan());
123        }
124        System.out.println();
125    }
126
127    // -------------------------------------------------------------------------------------
128    //  SEALED + EXHAUSTIVE SWITCH'in GÜCÜ:
129    //  Derleyici, Sekil'in TÜM alt tiplerini bildiği için, switch'in tüm durumları
130    //  kapsadığını kontrol edebilir. (Java 21'de pattern matching switch ile birleşince
131    //  "default" yazmaya bile gerek kalmaz; aşağıda klasik instanceof ile gösterildi
132    //  ki Java 17'de de derlensin.)
133    // -------------------------------------------------------------------------------------
134    static String aciklama(Sekil s) {
135        // Java 16+ pattern matching for instanceof kullanıyoruz (Java 17'de kalıcı).
136        if (s instanceof Daire d) {
137            return "Daire(r=" + d.yaricap + ")";
138        } else if (s instanceof Kare k) {
139            return "Kare(a=" + k.kenar + ")";
140        } else if (s instanceof Ucgen u) {
141            return "Ucgen";
142        }
143        // sealed sayesinde teorik olarak buraya hiç gelinmez; yine de güvenlik için:
144        throw new IllegalStateException("Bilinmeyen şekil: " + s);
145    }
146
147
148    // =====================================================================================
149    //  ÖRNEK 2: GERÇEK HAYAT - Ödeme Türü Hiyerarşisi
150    // =====================================================================================
151    //  Bir ödeme sisteminde ödeme yöntemleri SABİT ve BİLİNEN bir küme olmalıdır:
152    //  KrediKarti, Havale, Nakit. Dışarıdan yeni bir ödeme türü eklenmesi
153    //  (örn. üçüncü partinin "BitcoinÖdeme" eklemesi) iş kuralları açısından TEHLİKELİDİR.
154    //  Sealed ile bu kümeyi MÜHÜRLERİZ; sistem her zaman tüm olasılıkları bilir.
155    // =====================================================================================
156
157    sealed interface Odeme
158            permits KrediKarti, Havale, Nakit {
159        double tutar();
160    }
161
162    static record KrediKarti(double tutar, String kartNo) implements Odeme {}
163    static record Havale(double tutar, String iban) implements Odeme {}
164    static record Nakit(double tutar) implements Odeme {}
165
166    static void odemeDemosu() {
167        System.out.println("----- ÖRNEK 2: GERÇEK HAYAT - ÖDEME TÜRÜ -----");
168
169        Odeme[] odemeler = {
170                new KrediKarti(250.0, "1234-****-****-5678"),
171                new Havale(1000.0, "TR12 0001 0000 ..."),
172                new Nakit(75.50)
173        };
174
175        for (Odeme o : odemeler) {
176            System.out.printf("%-12s -> %s%n", o.getClass().getSimpleName(), islemUcreti(o));
177        }
178        System.out.println();
179        System.out.println("AVANTAJ: 'Odeme' mühürlü olduğu için ileride bir geliştirici");
180        System.out.println("yeni bir ödeme türü eklerse, islemUcreti() içindeki switch'i");
181        System.out.println("güncellemediği takdirde DERLEYİCİ uyarır/hata verir.");
182        System.out.println("Böylece 'unutulan durum' hatası daha derleme aşamasında yakalanır.");
183    }
184
185    // Mühürlü arayüz + pattern matching switch = exhaustive (tüm durumları kapsayan) kontrol.
186    // Tüm Odeme alt tipleri kapsandığı için derleyici 'default' istemez (Java 21).
187    static String islemUcreti(Odeme o) {
188        // switch ile pattern matching (Java 21 standardı). Daha eski sürümlerde
189        // bunu if-instanceof zinciriyle yazmak gerekirdi.
190        return switch (o) {
191            case KrediKarti k -> "Komisyon %2 -> " + (k.tutar() * 0.02) + " TL";
192            case Havale h     -> "Sabit ücret 5 TL";
193            case Nakit n      -> "Ücretsiz";
194        };
195    }
196}
Çıktı yerel JDK 21 ile yakalandı — tarayıcıda JVM çalışmaz.

TextBlocksKalici

çalıştırılabilir
TextBlocksKalici.java
1// =====================================================================================
2//  TextBlocksKalici.java
3//  Java 15 - Text Blocks (Metin Blokları) ARTIK KALICI (STANDART) ÖZELLİK
4// =====================================================================================
5//
6//  ÖNEMLİ SÜRÜM NOTU:
7//  ------------------
8//  - Text Blocks ilk olarak Java 13'te PREVIEW (önizleme) olarak geldi (JEP 355).
9//  - Java 14'te yine PREVIEW olarak devam etti (JEP 368, küçük iyileştirmelerle).
10//  - Java 15'te (Eylül 2020, JEP 378) artık KALICI / STANDART hale geldi!
11//
12//  Bunun pratik anlamı:
13//  - Java 13 ve 14'te bu özelliği kullanmak için derlerken ve çalıştırırken
14//    "--enable-preview" bayrağı GEREKİYORDU.
15//  - Java 15 ve sonrasında HİÇBİR ÖZEL BAYRAK GEREKMİYOR. Tıpkı normal bir
16//    String gibi doğrudan kullanılabilir.
17//
18//  DERLEME (Java 15+):
19//      javac TextBlocksKalici.java
20//      java  TextBlocksKalici
21//  (Dikkat: --enable-preview YOK. Çünkü artık kalıcı bir dil özelliği.)
22//
23// =====================================================================================
24
25public class TextBlocksKalici {
26
27    public static void main(String[] args) {
28
29        System.out.println("===== JAVA 15 TEXT BLOCKS (KALICI) DEMOSU =====\n");
30
31        // -----------------------------------------------------------------------------
32        // 1) TEXT BLOCK NEDİR?
33        // -----------------------------------------------------------------------------
34        // Text Block, üç adet çift tırnakla (""") başlayıp biten, ÇOK SATIRLI bir
35        // string yazma biçimidir. Açılış """ işaretinden sonra MUTLAKA yeni satıra
36        // geçilmelidir. İçerikteki satır sonları (\n) otomatik olarak eklenir.
37        //
38        // ESKİDEN (klasik String) çok satırlı metin yazmak "kaçış karakteri cehennemi"
39        // demekti: her satır için \n, her tırnak için \" yazmak gerekiyordu.
40
41        ornek1_BasitKarsilastirma();
42        ornek2_JsonOrnegi();
43        ornek3_SqlOrnegi();
44        ornek4_OzelDavranislar();
45        ornek5_GercekHayat();
46    }
47
48    // =====================================================================================
49    //  ÖRNEK 1: ESKİ vs YENİ - BASİT KARŞILAŞTIRMA
50    // =====================================================================================
51    static void ornek1_BasitKarsilastirma() {
52        System.out.println("----- ÖRNEK 1: ESKİ vs YENİ -----");
53
54        // ---- ESKİ YÖNTEM (Java 12 ve öncesi) - "kaçış karakteri cehennemi" ----
55        // Her satır sonunda \n, satırları birleştirmek için + operatörü.
56        String eskiYontem = "Merhaba Dünya!\n" +
57                            "Bu metin\n" +
58                            "birden fazla\n" +
59                            "satırdan oluşuyor.\n";
60
61        // ---- YENİ YÖNTEM (Java 15 - kalıcı Text Block) ----
62        // Açılış """ işaretinden sonra yeni satıra geçildi. \n YOK, + YOK.
63        // Çok daha okunabilir ve metnin gerçek hali nasılsa kodda da öyle görünür.
64        String yeniYontem = """
65                Merhaba Dünya!
66                Bu metin
67                birden fazla
68                satırdan oluşuyor.
69                """;
70
71        System.out.println(">> Eski yöntem çıktısı:");
72        System.out.print(eskiYontem);
73        System.out.println(">> Yeni yöntem çıktısı:");
74        System.out.print(yeniYontem);
75
76        // İki yöntem aynı metni üretir mi? Evet!
77        System.out.println(">> İki metin eşit mi? " + eskiYontem.equals(yeniYontem));
78        System.out.println();
79    }
80
81    // =====================================================================================
82    //  ÖRNEK 2: JSON YAZIMI - Text Block'un parladığı yer
83    // =====================================================================================
84    static void ornek2_JsonOrnegi() {
85        System.out.println("----- ÖRNEK 2: JSON ÖRNEĞİ -----");
86
87        // ---- ESKİ YÖNTEM: JSON içindeki her çift tırnak için \" yazmak zorundayız ----
88        String eskiJson = "{\n" +
89                          "  \"ad\": \"Ahmet\",\n" +
90                          "  \"soyad\": \"Yılmaz\",\n" +
91                          "  \"yas\": 30,\n" +
92                          "  \"adres\": {\n" +
93                          "    \"sehir\": \"İstanbul\",\n" +
94                          "    \"ulke\": \"Türkiye\"\n" +
95                          "  }\n" +
96                          "}";
97
98        // ---- YENİ YÖNTEM: Çift tırnaklar olduğu gibi yazılır, kaçış gerekmez! ----
99        // JSON, kopyala-yapıştır ile doğrudan kullanılabilir hale gelir.
100        String yeniJson = """
101                {
102                  "ad": "Ahmet",
103                  "soyad": "Yılmaz",
104                  "yas": 30,
105                  "adres": {
106                    "sehir": "İstanbul",
107                    "ulke": "Türkiye"
108                  }
109                }""";
110
111        System.out.println(">> Yeni Text Block ile JSON:");
112        System.out.println(yeniJson);
113        System.out.println();
114        System.out.println("(Eski yöntemde her \" karakterini \\\" olarak kaçırmak gerekirdi.)");
115        System.out.println();
116    }
117
118    // =====================================================================================
119    //  ÖRNEK 3: SQL YAZIMI - Çok satırlı sorgular okunaklı olur
120    // =====================================================================================
121    static void ornek3_SqlOrnegi() {
122        System.out.println("----- ÖRNEK 3: SQL ÖRNEĞİ -----");
123
124        // ---- ESKİ YÖNTEM: + ile satır birleştirme, boşluklara dikkat etmek gerek ----
125        String eskiSql = "SELECT m.ad, m.soyad, d.ad AS departman " +
126                        "FROM musteriler m " +
127                        "INNER JOIN departmanlar d ON m.departman_id = d.id " +
128                        "WHERE m.aktif = true " +
129                        "ORDER BY m.soyad ASC";
130
131        // ---- YENİ YÖNTEM: Sorgu, bir SQL editöründeki gibi hizalı ve okunaklı ----
132        String yeniSql = """
133                SELECT m.ad, m.soyad, d.ad AS departman
134                FROM musteriler m
135                INNER JOIN departmanlar d ON m.departman_id = d.id
136                WHERE m.aktif = true
137                ORDER BY m.soyad ASC""";
138
139        System.out.println(">> Yeni Text Block ile SQL sorgusu:");
140        System.out.println(yeniSql);
141        System.out.println();
142    }
143
144    // =====================================================================================
145    //  ÖRNEK 4: TEXT BLOCK ÖZEL DAVRANIŞLARI (incelik detayları)
146    // =====================================================================================
147    static void ornek4_OzelDavranislar() {
148        System.out.println("----- ÖRNEK 4: ÖZEL DAVRANIŞLAR -----");
149
150        // (a) "Incidental white space" (Tesadüfi boşluk) silinmesi:
151        // Java, en soldaki ortak girintiyi (en az girintili satıra göre) otomatik siler.
152        // Kapanış """ işaretinin konumu da girinti referansını etkiler.
153        String girintiOrnegi = """
154                    Satır 1
155                    Satır 2
156                """;
157        System.out.println(">> Girinti otomatik temizlenir:");
158        System.out.print(girintiOrnegi);
159
160        // (b) Satır sonu birleştirme: Satır sonunda \ kullanılırsa o satır,
161        // bir SONRAKİ satırla BİRLEŞTİRİLİR (yeni satır eklenmez).
162        // Çok uzun tek satırlık metinleri kodda bölmek için harikadır.
163        String tekSatir = """
164                Bu çok uzun bir cümle \
165                ama tek satır olarak \
166                birleştirilecek.""";
167        System.out.println(">> Satır sonu '\\' ile birleştirme:");
168        System.out.println(tekSatir);
169
170        // (c) Sonda boşluk korumak için \s kaçış dizisi (bir boşluk anlamına gelir):
171        String bosluk = """
172                Sonunda boşluk var:\s
173                İkinci satır""";
174        System.out.println(">> \\s ile sondaki boşluk korunur:");
175        System.out.println("[" + bosluk + "]");
176
177        // (d) Text Block normal bir String'dir; tüm String metotları çalışır.
178        String upper = """
179                küçük harf metin
180                """.toUpperCase();
181        System.out.println(">> Text Block üzerinde .toUpperCase(): " + upper);
182        System.out.println();
183    }
184
185    // =====================================================================================
186    //  ÖRNEK 5: GERÇEK HAYAT ÖRNEĞİ - HTML E-Posta Şablonu Üretimi
187    // =====================================================================================
188    static void ornek5_GercekHayat() {
189        System.out.println("----- ÖRNEK 5: GERÇEK HAYAT - HTML E-POSTA ŞABLONU -----");
190
191        // Senaryo: Bir e-ticaret sistemi, sipariş onayı için HTML e-posta gönderiyor.
192        // Eskiden bu tür HTML şablonlarını Java string olarak yazmak korkunçtu
193        // (her tırnak kaçırılır, her satır +'lı, her \n elle eklenirdi).
194        // Text Block ile HTML, bir .html dosyasındaki gibi doğal görünür.
195
196        String musteriAdi = "Zeynep Kaya";
197        String siparisNo   = "SP-2026-00891";
198        double tutar       = 1499.90;
199
200        // Not: %s ve %.2f yer tutucularını String.formatted (Java 15'te String'e eklendi)
201        // ile dolduruyoruz. formatted(), String.format ile aynı işi yapar ama zincirlenebilir.
202        String htmlEposta = """
203                <html>
204                  <body style="font-family: Arial, sans-serif;">
205                    <h2>Sipariniz Alındı!</h2>
206                    <p>Sayın <b>%s</b>,</p>
207                    <p>Sipariş numaranız: <code>%s</code></p>
208                    <p>Toplam tutar: <b>%.2f TL</b></p>
209                    <hr/>
210                    <p style="color: gray;">Bizi tercih ettiğiniz için teşekkürler.</p>
211                  </body>
212                </html>
213                """.formatted(musteriAdi, siparisNo, tutar);
214
215        System.out.println(">> Üretilen HTML e-posta:");
216        System.out.println(htmlEposta);
217
218        System.out.println("AÇIKLAMA: Aynı HTML'i eski yöntemle yazsaydık, onlarca \\\" ve");
219        System.out.println("\\n ile dolu, okunması imkansız bir string elde ederdik.");
220        System.out.println("\n===== DEMO SONU =====");
221    }
222}
Çıktı yerel JDK 21 ile yakalandı — tarayıcıda JVM çalışmaz.