Sealed Classes ve Records
Sealed Class Nedir?
Sealed class, kendisinden kimlerin türeyebileceğini kontrol eden bir sınıftır. Normalde bir sınıfı final yapmazsan herkes extend edebilir. Sealed class, "sadece şu sınıflar benden türeyebilir" demenin yolu.
Bunu bir apartman kapısı gibi düşün. Normal sınıf: kapı sonuna kadar açık, herkes girebilir. Final sınıf: kapı kilitli, kimse giremez. Sealed sınıf: kapıda güvenlik var, sadece listede olanlar girebilir.
Sealed Class Sözdizimi
sealed class Shape permits Circle, Rectangle, Triangle {
String color;
Shape(String color) {
this.color = color;
}
}
final class Circle extends Shape {
double radius;
Circle(String color, double radius) {
super(color);
this.radius = radius;
}
}
final class Rectangle extends Shape {
double width, height;
Rectangle(String color, double w, double h) {
super(color);
this.width = w;
this.height = h;
}
}
final class Triangle extends Shape {
double base, height;
Triangle(String color, double base, double height) {
super(color);
this.base = base;
this.height = height;
}
}
// class Pentagon extends Shape { } // HATA! permits listesinde yoksealed keyword'ü sınıfın mühürlü olduğunu, permits ise hangi sınıfların türeyebileceğini belirtir.
Permits Alt Sınıf Kuralları
permits listesindeki her sınıf şu üç modifier'dan birini kullanmak zorunda:
final — Bu sınıftan artık kimse türeyemez (zincir burada biter)
sealed — Bu sınıf da kendi alt sınıflarını kontrol eder (zincir devam)
non-sealed — Bu sınıftan herkes türeyebilir (zincir açılır)
sealed class Vehicle permits Car, Truck, Motorcycle {
}
final class Motorcycle extends Vehicle {
// Kimse Motorcycle'dan türeyemez
}
sealed class Car extends Vehicle permits Sedan, SUV {
// Sadece Sedan ve SUV türeyebilir
}
non-sealed class Truck extends Vehicle {
// Herkes Truck'tan türeyebilir
}
final class Sedan extends Car { }
final class SUV extends Car { }
class PickupTruck extends Truck { } // OK! Truck non-sealed
class MonsterTruck extends Truck { } // OK!Sealed Class Neden Kullanışlı?
1. Tüm alt tipleri bilmek:
Sealed class ile compiler tüm olası alt tipleri biliyor. Bu, switch ifadelerinde exhaustiveness (tamlık) kontrolü sağlar.
sealed interface PaymentMethod permits CreditCard, Cash, BankTransfer { }
record CreditCard(String number) implements PaymentMethod { }
record Cash(double amount) implements PaymentMethod { }
record BankTransfer(String iban) implements PaymentMethod { }// Java 21+ pattern matching switch
String describe(PaymentMethod pm) {
return switch (pm) {
case CreditCard cc -> "Card: " + cc.number();
case Cash c -> "Cash: " + c.amount();
case BankTransfer bt -> "Transfer: " + bt.iban();
// default gerekmiyor! Compiler tüm case'lerin kapsandığını biliyor
};
}Yeni bir PaymentMethod eklersen, bu switch ifadesi derleme hatası verir — hiçbir case'i kaçırmazsın.
2. Domain modelleme:
sealed interface HttpResponse permits Success, ClientError, ServerError { }
record Success(int code, String body) implements HttpResponse { }
record ClientError(int code, String message) implements HttpResponse { }
record ServerError(int code, String message) implements HttpResponse { }3. Güvenlik:
Kütüphane yazarı olarak alt sınıflandırmayı kontrol edebilirsin. Kimsenin beklenmedik bir alt sınıf oluşturarak sistemi bozmasını önlersin.
💡 İpucu: Sealed class'lar "algebraic data types" kavramının Java karşılığıdır. Fonksiyonel programlama dillerindeki (Haskell, Rust, Kotlin)
enum+data classkombinasyonuna benzer.
Sealed Interface
Interface'ler de sealed olabilir:
sealed interface Result<T> permits Success, Failure {
}
record Success<T>(T value) implements Result<T> { }
record Failure<T>(String error) implements Result<T> { }Result<String> result = new Success<>("Hello");
String output = switch (result) {
case Success<String> s -> "OK: " + s.value();
case Failure<String> f -> "Error: " + f.error();
};
System.out.println(output); // OK: HelloRecord Sınıflar Nedir?
Record, sadece veri taşıyan sınıflar için kısa yol sözdizimidir. Java 16 ile geldi. Getter, constructor, equals(), hashCode() ve toString() otomatik oluşturulur.
Düşün ki bir veri kartı yapıyorsun — ad, soyad, numara. Bunun için 50 satır boilerplate kod yazmak yerine, record ile 1 satırda halledersin.
Record Sözdizimi
// Eski yol — ~40 satır
class Point {
private final int x;
private final int y;
Point(int x, int y) {
this.x = x;
this.y = y;
}
int x() { return x; }
int y() { return y; }
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Point p)) return false;
return x == p.x && y == p.y;
}
@Override
public int hashCode() {
return Objects.hash(x, y);
}
@Override
public String toString() {
return "Point[x=" + x + ", y=" + y + "]";
}
}// Record ile — 1 satır!
record Point(int x, int y) { }Bu tek satır, yukarıdaki tüm kodu otomatik oluşturur. Aynı işlevsellik, minimum yazım.
Point p1 = new Point(3, 5);
Point p2 = new Point(3, 5);
System.out.println(p1.x()); // 3 (getter — x() şeklinde)
System.out.println(p1.y()); // 5
System.out.println(p1); // Point[x=3, y=5]
System.out.println(p1.equals(p2)); // true (değer karşılaştırması)
System.out.println(p1.hashCode() == p2.hashCode()); // trueRecord'un Özellikleri
Record'lar immutable (değiştirilemez). Alanlar private final'dır.
record Student(String name, int id, double gpa) { }
Student s = new Student("Ali", 101, 3.75);
// s.name = "Veli"; // HATA! final alan
// Setter yok — değiştirmek istersen yeni nesne oluşturRecord'ların kısıtlamaları:
Başka sınıftan extend edemez (implicit olarak
Recordsınıfından türer)Instance alanı eklenemez (sadece header'daki parametreler)
abstractolamazAma interface implement edebilir
interface Printable {
void print();
}
record Product(String name, double price) implements Printable {
@Override
public void print() {
System.out.println(name + ": " + price + " TL");
}
}Compact Constructor
Record'larda validasyon yapmak için compact constructor kullanabilirsin. Parametreleri ve atamaları tekrar yazmana gerek yok.
record Age(int value) {
// Compact constructor — parametre listesi yok
Age {
if (value < 0 || value > 150) {
throw new IllegalArgumentException("Invalid age: " + value);
}
}
}Age valid = new Age(25); // OK
Age invalid = new Age(-5); // IllegalArgumentException!Compact constructor'da this.value = value; yazmana gerek yok — Java bunu otomatik yapar. Sen sadece validasyon veya dönüşüm mantığını yazarsın.
record Email(String address) {
Email {
// Validasyon
if (address == null || !address.contains("@")) {
throw new IllegalArgumentException("Invalid email: " + address);
}
// Dönüşüm — normalizasyon
address = address.toLowerCase().trim();
}
}Email e = new Email(" Ali@Mail.COM ");
System.out.println(e.address()); // ali@mail.com⚠️ Dikkat: Compact constructor'da parametreye yeniden atama yapabilirsin (
address = address.toLowerCase()). Java, constructor sonunda bu değerithis.address'e otomatik atar.
Record ile Custom Metotlar
Record'lara kendi metotlarını ekleyebilirsin:
record Rectangle(double width, double height) {
double area() {
return width * height;
}
double perimeter() {
return 2 * (width + height);
}
Rectangle scale(double factor) {
return new Rectangle(width * factor, height * factor);
}
}Rectangle r = new Rectangle(4, 5);
System.out.println(r.area()); // 20.0
System.out.println(r.perimeter()); // 18.0
Rectangle bigger = r.scale(2);
System.out.println(bigger); // Rectangle[width=8.0, height=10.0]Sealed + Record Birlikte
Sealed class/interface ve record birlikte kullanıldığında çok güçlü domain modelleri oluşur.
sealed interface Expression permits
Literal, Add, Multiply, Negate {
}
record Literal(double value) implements Expression { }
record Add(Expression left, Expression right) implements Expression { }
record Multiply(Expression left, Expression right) implements Expression { }
record Negate(Expression expr) implements Expression { }double evaluate(Expression expr) {
return switch (expr) {
case Literal l -> l.value();
case Add a -> evaluate(a.left()) + evaluate(a.right());
case Multiply m -> evaluate(m.left()) * evaluate(m.right());
case Negate n -> -evaluate(n.expr());
};
}// (3 + 5) * 2
Expression expr = new Multiply(
new Add(new Literal(3), new Literal(5)),
new Literal(2)
);
System.out.println(evaluate(expr)); // 16.0Bu pattern, compiler-yazımı, matematiksel ifade değerlendirme, AST (Abstract Syntax Tree) gibi alanlarda çok kullanılır.
Ne Zaman Record Kullanılır?
Record kullan:
Sadece veri taşıyan sınıflar (DTO, Value Object)
Immutability istediğin durumlar
equals()/hashCode()/toString()boilerplate'inden kurtulmak istediğindeAPI response/request modelleri
Record kullanma:
Değiştirilebilir (mutable) duruma ihtiyaç varsa
Kalıtım gerekiyorsa (başka sınıftan extend etmek)
Karmaşık iş mantığı olan sınıflar
// İyi record kullanımı
record Coordinate(double lat, double lon) { }
record Money(BigDecimal amount, String currency) { }
record ApiResponse(int status, String body) { }
// Kötü record kullanımı — bunlar record olmamalı
// record UserService(...) { } — servis sınıfı
// record DatabaseConnection(...) { } — durum yönetimi varNe Zaman Sealed Class Kullanılır?
Sealed class kullan:
Sabit sayıda alt tip varsa (enum yetmediğinde)
Pattern matching ile exhaustive switch istiyorsan
Kütüphane/framework yazıyorsan ve alt sınıflandırmayı kontrol etmek istiyorsan
Domain modelinde olası tipleri kısıtlamak istiyorsan
// Enum yetmiyor çünkü her variant farklı veri taşıyor
sealed interface Shape permits Circle, Rectangle {
}
record Circle(double radius) implements Shape { }
record Rectangle(double w, double h) implements Shape { }
// Enum yeterli — her değer aynı yapıda
enum Color { RED, GREEN, BLUE }💡 İpucu: Sealed class'lar ve record'lar Java'nın modern yüzü. Java 17+ projelerde bu özellikler DTO'lar, domain modeller ve pattern matching ile birlikte yoğun şekilde kullanılıyor.
Gerçekçi Örnek: JSON Parse Sonucu
sealed interface JsonValue permits
JsonString, JsonNumber, JsonBoolean, JsonNull, JsonArray, JsonObject {
}
record JsonString(String value) implements JsonValue { }
record JsonNumber(double value) implements JsonValue { }
record JsonBoolean(boolean value) implements JsonValue { }
record JsonNull() implements JsonValue { }
record JsonArray(List<JsonValue> elements) implements JsonValue { }
record JsonObject(Map<String, JsonValue> fields) implements JsonValue { }String stringify(JsonValue value) {
return switch (value) {
case JsonString s -> "\"" + s.value() + "\"";
case JsonNumber n -> String.valueOf(n.value());
case JsonBoolean b -> String.valueOf(b.value());
case JsonNull ignored -> "null";
case JsonArray a -> "[" +
a.elements().stream()
.map(e -> stringify(e))
.reduce((x, y) -> x + ", " + y)
.orElse("") + "]";
case JsonObject o -> "{...}";
};
}Bu örnekte sealed interface tüm olası JSON tiplerini tanımlıyor, record'lar her tip için veriyi taşıyor, pattern matching ile güvenli bir şekilde işleniyor. Yeni bir JSON tipi eklersen (olası değil ama), tüm switch ifadeleri seni uyarır.
Özet
Sealed class (
sealed ... permits), kendisinden türeyebilecek sınıfları kısıtlar; alt sınıflarfinal,sealedveyanon-sealedolmalıdır.Sealed class'lar compiler'a tüm alt tipleri bildirdiği için exhaustive switch (tamlık kontrolü) mümkün olur.
Record (
record Name(params)) sadece veri taşıyan immutable sınıflar için kısa yol sözdizimidir; constructor, getter,equals(),hashCode(),toString()otomatik oluşturulur.Compact constructor ile record'larda validasyon ve normalizasyon yapılabilir.
Sealed + record birlikte kullanıldığında güçlü algebraic data types oluşur — domain modelleme ve pattern matching için ideal.
Record kullan: DTO, value object, API modelleri. Kullanma: mutable state, karmaşık iş mantığı, kalıtım gereken durumlar.
AI Asistan
Sorularını yanıtlamaya hazır