Logo
Ana Sayfa
ÖZEL KODLAMA DERSLERİ
Yazılım Özel Ders
Tüm birebir programlara genel bakış
Python Yazılım Kursu
Sıfırdan ileri seviyeye birebir Python
Java Yazılım Kursu
OOP odaklı birebir Java eğitimi
AP Computer Science Principles
AP CSP sınav hazırlığı
GRUP DERSLERİ
Python & Django Masterclass
SINIRLI KONTENJAN
Java & Spring Boot Masterclass
SINIRLI KONTENJAN
C# .NET Masterclass
SINIRLI KONTENJAN
VİDEO DERSLER
Sıfırdan Temel Python Kursu
Kendi Hızında Öğren
ÜCRETSİZ
Seviye Testi
Ücretsiz — Python, Java, algoritma seviye testleri
Kariyerini Keşfet
Sertifika Doğrula
Belge numarası ve soyad ile doğrulama

Java Record Veri Sınıfları: Klasik POJO'ya Kıyasla Ne Kazandırır?

Yazar: Berk Keskin 28.08.2026 ~12 dk okuma 1 Okunma
java-record-veri-siniflari-pojo-karsilastirmasi

Java record, veri taşımak amacıyla tasarlanmış, alanlarını otomatik olarak final yapan ve klasik bir sınıfta elle yazman gereken equals, hashCode, toString gibi metotları derleyiciye bıraktıran özel bir sınıf türüdür. Kısacası bir record, "bu sınıfın tek görevi veri taşımak, davranış değil" dediğin anda devreye giren bir kalıptır. Klasik POJO ile record arasındaki fark bir üstünlük meselesinden çok, aynı işi daha az kod tekrarıyla yapabilme meselesidir; her iki yaklaşım da yerine göre haklı çıkabilir.

Bir sınıfı geleneksel yoldan yazdığında alanları tanımlar, constructor'ı elle kurar, getter'ları tek tek eklersin ve genellikle equals/hashCode/toString metotlarını da IDE'ye ürettirirsin. Record bu adımların büyük kısmını tek bir bildirimde toplar. Örneğin bir ürün bilgisini taşıyan sınıf record ile şu kadar kısa yazılabilir:

public record Product(String name, double price) {}

Bu tek satır, aşağıda göreceğin gibi arka planda birden fazla metodu otomatik olarak üretir. Amaç sihir yapmak değil; veri odaklı sınıflarda tekrar eden, hata payı taşıyan kodu ortadan kaldırmaktır.

Derleyicinin Otomatik Ürettiği Metotlar

Yukarıdaki Product record'unu tanımladığın anda derleyici senin için dört şeyi otomatik üretir: her alan için bir accessor metodu (name(), price() gibi; klasik getName() değil), tüm alanları karşılaştıran bir equals, buna tutarlı bir hashCode ve alan değerlerini okunabilir biçimde gösteren bir toString. Klasik bir POJO'da bu dört metodu elle yazarsan, alan sayısı arttıkça unutulan bir alanın equals veya hashCode'a dahil edilmemesi gibi küçük ama etkisi büyük hatalar ortaya çıkabilir; IDE üretimi bile yeni bir alan eklendiğinde metotları yeniden üretmeyi hatırlamayı gerektirir.

Record'da bu senkronizasyon sorunu yapısal olarak ortadan kalkar çünkü metotlar alan listesinden türetilir, alan listesi değiştiğinde metotlar da otomatik güncellenir. Bunun getirisi büyülü bir "daha iyi kod" iddiası değil, somut olarak daha az satır ve daha az bakım noktasıdır: aynı üç alanlı bir sınıf için klasik yaklaşımda 40-50 satıra yayılabilecek kod, record'da tek satıra iner ve okuyan kişi sınıfın neyi temsil ettiğini alan listesine bakarak anında görür. Bu tür temel OOP yapı taşlarının ne kadar sağlam oturduğunu ölçmek isteyenler Java bilgi seviyeni ölçen test ile kendi hakimiyetini kontrol edebilir.

Burada dikkat edilmesi gereken nokta, üretilen equals ve hashCode'un tüm alanları eşit ağırlıkla değerlendirmesidir; yani iki alanın sadece bir kısmına göre eşitlik tanımlamak istiyorsan record'un varsayılan davranışı senin ihtiyacına uymayabilir. Bu durumda ya alan seçimini yeniden düşünmen ya da klasik sınıfa dönüp equals'ı elle özelleştirmen gerekir.

Compact Constructor ile Doğrulama Mantığı Ekleme

Record'un alan listesini tekrar yazmadan constructor içine kod ekleyebileceğin özel bir yapı vardır: compact constructor. Normal bir constructor'da parametre listesini ve atama satırlarını (this.name = name; gibi) elle yazman gerekirken, compact constructor'da parantez içi parametre listesi yazılmaz; sadece doğrulama veya normalize etme kodu yazılır, alanlara atama derleyici tarafından örtük olarak yapılır.

Bu yapı özellikle negatif değer veya boş alan gibi geçersiz durumları daha nesne oluşturulmadan engellemek için kullanılır:

public record Product(String name, double price) {
    public Product {
        if (name == null || name.isBlank()) {
            throw new IllegalArgumentException("Ürün adı boş olamaz");
        }
        if (price < 0) {
            throw new IllegalArgumentException("Fiyat negatif olamaz");
        }
    }
}

// Kullanım:
// Product p1 = new Product("Klavye", 450.0); // geçerli
// Product p2 = new Product("", -10.0);        // IllegalArgumentException fırlatır

Doğrulamanın accessor metotlarına (name(), price()) değil constructor'a bağlanmasının nedeni, record'un immutable yapısıyla doğrudan ilgilidir. Bir nesne bir kez oluşturulduktan sonra alanları değişmeyeceğine göre, geçersiz veri constructor aşamasında engellenmezse o nesne yaşam süresi boyunca hatalı veri taşımaya devam eder. Accessor'a kontrol koymak, zaten var olan hatalı veriyi her okumada yeniden fark etmekten öteye geçmez; oysa compact constructor sayesinde hatalı nesne baştan hiç oluşmaz. Bu, klasik bir POJO'da da setter'sız bir constructor ile taklit edilebilir bir davranıştır, ama record bunu dile özgü, ayrı bir disiplin gerektirmeden sağlar.

Record Neden Immutable Olmak Zorunda?

Bir record tanımlandığı anda, derleyici bileşen listesindeki her alanı otomatik olarak final yapar. Bu bir tasarım tercihi değil, dilin record'a yüklediği temel bir kuraldır: bir kez oluşturulan record nesnesinin hiçbir alanı sonradan değiştirilemez. Örneğin record Musteri(String ad, String email) tanımlandığında, ne ad ne de email için bir setter üretilir; bu alanlara yalnızca constructor aşamasında değer verilebilir ve sonrasında salt okunur kalır.

Bu davranış, önceki bölümlerde ele alınan immutable sınıf tasarımı mantığıyla doğrudan örtüşür. Bir nesnenin durumu oluşturulduktan sonra değişmiyorsa, o nesne birden fazla iş parçacığı (thread) tarafından aynı anda okunduğunda veri bütünlüğü bozulma riski taşımaz; senkronizasyon bloklarına veya kilit mekanizmalarına ihtiyaç duymadan güvenle paylaşılabilir. Aynı şekilde bir metoda parametre olarak geçirilen record nesnesinin, metot içinde beklenmedik şekilde değiştirilmeyeceğini bilmek, kod okurken öngörülebilirliği artırır — bir nesneyi elinize aldığınızda, üç satır sonra hâlâ aynı değerleri taşıdığından emin olabilirsiniz.

Ancak bu güvence bedelsiz değildir. Alanların değeri zamanla değişmesi gereken senaryolarda — örneğin bir sipariş nesnesinin durumu "hazırlanıyor"dan "kargoya verildi"ye geçiyorsa — record uygun bir seçim olmaktan çıkar, çünkü her durum değişikliği için yeni bir nesne üretmek zorunda kalırsınız. Bu tür mutasyon ihtiyacı olan senaryolarda hangi yapının tercih edilmesi gerektiğini ilerleyen bölümde daha ayrıntılı ele alacağız. Bu tür immutable ve mutable sınıf ayrımını pratikte oturtmak isteyen öğrenciler için birebir Java dersleri kapsamında bu konular canlı kod üzerinden işlenir. Şimdilik bilinmesi gereken temel nokta şudur: record'un immutable oluşu bir kısıtlama değil, belirli bir problem sınıfını (özellikle veri taşıma amaçlı nesneleri) çözmek için bilinçli bir tasarım kararıdır.

Kalıtım Kısıtları: Final Sınıf ve Interface Kullanımı

Record tanımladığınızda, o sınıf örtük olarak final kabul edilir; yani bir record'dan başka bir sınıf türetilemez. Aynı şekilde bir record, başka bir sınıftan da kalıtım alamaz — çünkü her record zaten örtük olarak temel bir kayıt türünden türemiş sayılır ve dil bu zinciri başka bir seviyeye açmaya izin vermez. Bu, klasik POJO tasarımında alışılmış olan "ortak alanları taşıyan bir üst sınıf oluştur, alt sınıflarda genişlet" yaklaşımının record'larda doğrudan uygulanamayacağı anlamına gelir.

Buna karşın record'lar bir veya birden fazla interface implemente edebilir; bu kısıt yalnızca sınıf kalıtımı için geçerlidir, davranış sözleşmeleri için değil. Örneğin bir Karsilastirilabilir arayüzü uygulanmak istendiğinde bu şu şekilde yazılabilir:

interface Karsilastirilabilir {
    int oncelikSirasi();
}

record Gorev(String baslik, int oncelik) implements Karsilastirilabilir {
    @Override
    public int oncelikSirasi() {
        return oncelik;
    }
}

Burada Gorev record'u hem verisini taşımaya devam eder hem de Karsilastirilabilir sözleşmesini yerine getirerek farklı sınıflarla ortak bir davranış üzerinden çalışabilir. Aynı yaklaşım birden fazla interface için de geçerlidir; virgülle ayırarak birden çok sözleşme aynı anda uygulanabilir.

Bu kısıt, özellikle nesne yönelimli tasarımda kalıtım hiyerarşisine dayanan sistemlerde önceden düşünülmesi gereken bir noktadır. Bir veri modelinin ileride alt sınıflarla genişletilmesi planlanıyorsa — örneğin Calisan sınıfından Yonetici ve Stajyer türetilecekse — bu hiyerarşiyi record ile kurmak mümkün değildir; böyle durumlarda klasik sınıf yapısı tercih edilmelidir. Kalıtım ve interface ayrımının nerede hangi tasarıma daha uygun düştüğünü örneklerle görmek isteyenler birebir Java özel dersleri aracılığıyla bu senaryoları kendi projeleri üzerinden çalışabilir. Kısacası record, "veri taşı ve davranış sözleşmesi uygula" ihtiyacına cevap verir; "hiyerarşik genişleme" ihtiyacına değil.

Klasik POJO ile Record'u Yan Yana Karşılaştırma

Aradaki farkı soyut biçimde anlatmak yerine aynı veri modelini iki şekilde yazıp karşılaştırmak daha öğreticidir. Aşağıda bir Urun modelinin klasik POJO hâli yer alıyor:

public class Urun {
    private final String ad;
    private final double fiyat;

    public Urun(String ad, double fiyat) {
        this.ad = ad;
        this.fiyat = fiyat;
    }

    public String getAd() { return ad; }
    public double getFiyat() { return fiyat; }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Urun)) return false;
        Urun u = (Urun) o;
        return Double.compare(u.fiyat, fiyat) == 0 && ad.equals(u.ad);
    }

    @Override
    public int hashCode() {
        return java.util.Objects.hash(ad, fiyat);
    }

    @Override
    public String toString() {
        return "Urun{ad=" + ad + ", fiyat=" + fiyat + "}";
    }
}

Aynı modelin record karşılığı şöyledir:

record Urun(String ad, double fiyat) { }

Yukarıdaki POJO tanımı yaklaşık yirmi beş satır sürerken, record karşılığı tek satırda aynı işlevi görür: alanlar, constructor, getter'lar, equals, hashCode ve toString derleyici tarafından otomatik üretilir. Bu fark sadece satır sayısıyla ilgili değildir; POJO versiyonunda altı farklı metodun elle yazılmış olması, ileride fiyat alanına yeni bir alan eklendiğinde equals ve hashCode metotlarının da güncellenmesi gerektiği anlamına gelir — bu adım unutulduğunda sessiz ve tespiti zor hatalar ortaya çıkar. Record'da ise yeni bileşen eklemek tek satırlık tanımı güncellemek demektir; türetilen metotlar otomatik olarak yeni alanı da kapsar.

Okunabilirlik açısından da fark belirgindir: record Urun(String ad, double fiyat) satırını okuyan bir geliştirici, modelin tam olarak hangi verileri taşıdığını ve bu verilerin değişmez olduğunu tek bakışta anlar. POJO versiyonunda aynı bilgiye ulaşmak için yirmi beş satırı tarayıp hangi alanların constructor'da atandığını, hangi metotların gerçekten iş mantığı içerdiğini ayırt etmek gerekir. Bu fark, özellikle çok sayıda küçük veri modelinin bir arada bulunduğu projelerde kod tabanının genel taranabilirliğini doğrudan etkiler.

Record vs Klasik Sınıf: Karşılaştırma Tablosu

Önceki bölümlerde tek tek incelenen farkları bir arada görmek, hangi aracın hangi problemi çözdüğünü netleştirir. Aşağıdaki tablo, record ile klasik POJO'yu dört temel kriter üzerinden yan yana koyar; amaç birini diğerine üstün ilan etmek değil, kullanım bağlamına göre doğru seçimi görünür kılmaktır.

Kriter Record Klasik Sınıf (POJO)
Otomatik metot üretimi equals, hashCode, toString ve alan erişimcileri derleyici tarafından otomatik üretilir Bu metotlar geliştirici tarafından elle yazılır veya IDE aracılığıyla üretilir
Immutability Tüm alanlar varsayılan olarak final'dir, nesne oluşturulduktan sonra değiştirilemez Alanlar mutable tanımlanabilir, setter metotlarıyla durum sonradan değiştirilebilir
Kalıtım Başka bir sınıftan kalıtım alamaz, kendisi de final olduğu için genişletilemez extends ile başka sınıftan türetilebilir ve kendisinden de yeni sınıflar türetilebilir
Kullanım senaryosu Sabit veri taşıyan yapılar: DTO, API yanıtı, değer nesneleri Davranış barındıran, zamanla durumu değişen iş nesneleri

Tablodaki ayrım aslında tek bir soruya indirgenebilir: nesnenin görevi veri taşımak mı, yoksa zaman içinde durumunu yönetmek mi? Cevap birinciyse record, ikinciyse klasik sınıf tasarımı daha uygun bir zemin sunar.

DTO ve API Yanıt Modellerinde Record Kullanımı

Bir servis katmanının dışarıya döndürdüğü yanıt modelini düşünelim: kullanıcı adı, e-posta ve kayıt tarihi gibi alanları taşıyan, hiçbir iş kuralı içermeyen basit bir yapı. Klasik POJO yaklaşımında bu yapı için özel alanlar, bir constructor, getter metotları ve genellikle equals/hashCode/toString tanımları yazılır. Aynı ihtiyaç record ile şu şekilde karşılanır:

public record KullaniciYanitDTO(
        String kullaniciAdi,
        String email,
        LocalDate kayitTarihi
) {}

Bu tek satırlık tanım, klasik POJO'da genellikle 25-30 satıra yayılan alan tanımı, constructor ve erişimci bloğunun yerini alır. Kod tekrarının azalması burada soyut bir vaat değil, doğrudan ölçülebilir bir satır farkıdır; aynı davranışı üreten kodun okunabilirliği de bu oranda artar. API katmanlarında onlarca farklı yanıt modeli tanımlandığı düşünüldüğünde, bu fark proje genelinde ciddi bir bakım yükü azalmasına dönüşür. Java'da nesne yönelimli tasarım pratiklerini uygulamalı örneklerle işleyen çalışmalarda bu tür DTO senaryoları, record'un pratikteki en somut kazanç alanı olarak öne çıkar.

Ancak bu kullanım her yanıt modeli için otomatik doğru değildir. Bir DTO'nun içine doğrulama ötesinde iş mantığı, hesaplama veya duruma bağlı davranış eklenmeye başlandığı an, o yapı artık "sadece veri taşıyan" bir nesne olmaktan çıkar. Böyle durumlarda ya davranışı ayrı bir servis sınıfına taşımak ya da klasik sınıf tasarımına dönmek, kod tabanının uzun vadede daha sağlıklı kalmasını sağlar.

Ne Zaman Record, Ne Zaman Klasik Sınıf veya Builder?

Bu seçim çoğu zaman "hangisi daha modern" sorusuyla değil, nesnenin projede üstlendiği rolle cevaplanır. Aşağıdaki sıralama, karar sürecinde sorulması gereken soruları önem sırasına göre listeler:

  1. Nesne davranış mı barındırıyor, yoksa sadece veri mi taşıyor? Hesaplama, doğrulama ötesi iş kuralı veya durum yönetimi varsa klasik sınıf daha uygun bir başlangıç noktasıdır.
  2. Nesnenin ömrü boyunca alanlarında mutasyon gerekiyor mu? Bir alanın sonradan güncellenmesi gerekiyorsa record'un immutable yapısı buna doğrudan izin vermez, klasik sınıf ya da farklı bir tasarım gerekir.
  3. Kalıtım hiyerarşisine ihtiyaç var mı? Nesnenin başka bir sınıftan türetilmesi veya kendisinin genişletilebilir olması gerekiyorsa record bu ihtiyacı karşılamaz.
  4. Alan sayısı ve opsiyonel parametre yoğunluğu ne durumda? Az sayıda zorunlu alan varsa record'un constructor'ı yeterlidir; alan sayısı arttıkça ve çoğu alan opsiyonel hale geldikçe, okunabilirlik için builder deseni hâlâ daha güvenli bir seçenektir.
  5. Nesne bir kütüphane veya framework tarafından reflection ile inşa ediliyor mu? Bu tür entegrasyonlarda klasik sınıf yapısı bazen daha öngörülebilir davranır.

Bu kriterler gösteriyor ki record, builder desenini tamamen devre dışı bırakan bir yapı değildir; aksine ikisi farklı problemleri çözer. Record kod tekrarını azaltırken, builder çok sayıda opsiyonel parametreyi okunabilir biçimde yönetir. Kendi seviyenizi ve bu ayrımı ne kadar netleştirdiğinizi görmek isterseniz Java bilgi seviyesi ölçen kısa testi çözerek konuyu pekiştirebilirsiniz.

Sık Sorulan Sorular

Java'da record ile class arasındaki temel fark nedir?

Record, alanları final olan ve equals, hashCode, toString gibi metotları derleyici tarafından otomatik üretilen, immutable bir veri taşıyıcısıdır. Klasik class ise bu metotları elle tanımlamayı gerektirir ve mutable alanlara, kalıtıma açık bir yapı sunar.

Record içine yeni metot eklenebilir mi?

Evet, record gövdesine ek metotlar, statik alanlar ve statik metotlar tanımlanabilir. Ancak yeni bir örnek alanı (instance field) eklenemez; alan tanımı yalnızca record başlığındaki parametre listesiyle sınırlıdır.

Record neden immutable olmak zorundadır?

Record'un tasarım amacı, bir değeri güvenli biçimde taşımaktır; alanların final olması, nesne bir kez oluşturulduktan sonra durumunun beklenmedik şekilde değişmesini engeller. Bu, özellikle çok iş parçacıklı ortamlarda ve veri bütünlüğünün önemli olduğu senaryolarda tercih edilen bir garantidir.

Bir record başka bir sınıftan kalıtım alabilir mi?

Hayır, her record örtük olarak final'dir ve yalnızca bir üst tipten (örneğin bir arayüzden) türeyebilir; başka bir sınıftan extends ile kalıtım alamaz ve kendisinden de yeni bir sınıf türetilemez.

Record hangi durumlarda tercih edilmemelidir?

Nesne zamanla durumunu değiştiriyorsa, karmaşık iş mantığı barındırıyorsa veya bir kalıtım hiyerarşisinin parçası olması gerekiyorsa record uygun bir seçim değildir; bu durumlarda klasik sınıf tasarımı daha sağlıklıdır.

Compact constructor ile normal constructor arasındaki fark nedir?

Compact constructor, record'un parametre listesini tekrar yazmadan doğrudan doğrulama ve normalize etme mantığı eklemeyi sağlar; alan atamaları derleyici tarafından otomatik yapılır. Normal constructor'da ise parametre listesi ve atama satırları elle yazılmak zorundadır.

Record'lar Builder deseninin yerini tamamen alır mı?

Hayır. Record, az sayıda zorunlu alanı olan basit veri yapıları için kod tekrarını azaltır; ancak çok sayıda opsiyonel parametre içeren, adım adım nesne kurulumu gerektiren senaryolarda builder deseni okunabilirlik açısından hâlâ tercih edilen bir yaklaşımdır.

Record ile POJO arasındaki bu ayrımı netleştirmek, sadece sözdizimini bilmekten çok, nesnenin projedeki rolünü doğru okumakla ilgilidir. Bu tür tasarım kararlarını gerçek kod örnekleriyle uygulamalı olarak pekiştirmek isteyenler canlı Java derslerine göz atabilir.

Bu içeriğin üretilmesinde yapay zeka araçlarından destek alınmıştır.

İlgili Eğitimler

Berk Keskin — Yazılım Geliştirici ve Eğitmen
Yazar

Berk Keskin Kimdir?

Yazılıma 12 yaşında başladı; bugün öğrencinin seviyesine ve hedefine göre şekillenen sürdürülebilir öğrenme sistemleri tasarlıyor. 300'den fazla kişiye ezber değil, düşünerek kod yazmayı öğretti — Berk Akademi'de izlemeye değil üretmeye dayalı öğrenme kültürünü o kuruyor.

WhatsApp Hemen Ara