---
title: Dizileştirme
source: 'https://academia.sh/tr/kurslar/java-standart-kutuphane/dizilestirme'
course: 'Standart Kütüphane ve Akışlar'
language: tr
updated: '2026-08-17T18:09:43+00:00'
license: 'CC BY-SA 4.0'
---

# Dizileştirme

Dizileştirmenin yapıcıyı hiç çalıştırmadığı ve yapıcının kurduğu değişmezlerin bu yüzden dışarıdan gelen bir bayt dizisiyle atlanabildiği ölçülür. Bir geri okuma kancasının denetimi nasıl geri getirdiği ve alan adıyla sürüm kimliğinin nasıl bir sözleşmeye dönüştüğü gösterilir.

Önceki ders bir dosyanın var olup olmadığını ve hangi baytları taşıdığını ölçtü — ama bir
nesnenin kendisi bayta çevrildiğinde ne olduğu hiç sorulmadı. Bu ders dizileştirmeye bakar.
Nesneye Dayalı Java kursu (M08/K02, `java-siniflar/01`, `02`) yapıcı zincirini ve sınıf
değişmezlerini kurdu: bir nesne ancak yapıcısından geçerek var olabilir, ve yapıcı değişmezi
zorlayan yerdir. Dizileştirme bu kuralın **dışında** durur — bir nesneyi bayta çevirip geri
okumak, yapıcıyı hiç çağırmadan bir nesne üretmenin standart kitaplıktaki yoludur.

Kursun sorusu burada yeniden sorulur: yapıcının kurduğu bir değişmez, geri okumada hâlâ
geçerli mi? Yanıtı bulmak için tek bir sınıf yeter, çünkü karşılaştırılan iki gerçekleştirim
değil, aynı sınıfın iki farklı yoldan — yapıcıdan ve dizileştirmeden — geçerek var olmasıdır.

Üç bölüm sırayla ilerliyor. İlk bölüm yapıcının atlandığını ve bunun bedelini sayıyor.
İkincisi bu bedelin nasıl geri kazanılabildiğini gösteriyor. Üçüncüsü ise değişmezlerin
ötesine geçip sınıfın **kendi şeklinin** — alan adlarının ve sürüm kimliğinin — geri okumada
nasıl bir sözleşmeye dönüştüğünü ölçüyor.

Bu, K03'ün "çağıranın yükümlülüğü" dediği kaynaklardan biridir, ama alışılmadık bir biçimde:
çağıran burada değişmezi kıran taraf değil, değişmezi **geri kazanmakla** yükümlü taraftır.
Kütüphane bir söz vermedi ve tutmadığı bir sözü de yok; boşluğu dolduracak satırı yazmak
baştan beri sınıfı tasarlayana aitti.

## Yapıcı Hiç Çalışmıyor

- **IO17** — `Hesap` sınıfı yapıcısında iki değişmez zorluyor: sahip adı boş olamaz, bakiye
  negatif olamaz. İkisi de tek bir `if` satırıyla, yapıcının içinde denetleniyor.
- **IO18** — Elde, yapıcı hiç çağrılmadan üretilmiş bir bayt dizisi var: sahibi boş, bakiyesi
  negatif bir `Hesap` kaydı. Bu derste o bayt dizisinin **nasıl** üretildiği önemli değil;
  önemli olan geri okunduğunda ne olduğu. Risk, yapıcının kurduğu değişmezlerden kaçının
  geri okumada geçerli kaldığı sayısıyla ölçülüyor.

`Serializable`'ı uygulayan **her** sınıf bu ölçümün konusu olabilir; arayüz hiçbir yöntem
istemez, yalnız bir işarettir. `Hesap` burada seçilmiş özel bir örnek değil, kütüphanenin
herhangi bir dizileştirilebilir sınıfa uyguladığı genel bir davranışın tek bir örneğidir.
Aşağıdaki ölçüm bu davranışı elde bulunan bir bayt dizisiyle gösteriyor; o dizinin bir
dosyadan mı, bir ağ bağlantısından mı, yoksa programın kendi önceki bir çalışmasından mı
geldiği ölçüm açısından fark etmiyor — hangi kaynaktan geldiğine bakmaksızın, geri okuma
aynı yolu izliyor.

```java
// YapiciAtlanir.java — dizilestirme yapiciyi hic calistirmaz, yapicinin kurdugu degismezler korunmaz
import java.io.*;
import java.util.Base64;

class Hesap implements Serializable {
    private static final long serialVersionUID = 1L;
    private final String sahip;
    private final long bakiye;

    Hesap(String sahip, long bakiye) {
        if (sahip == null || sahip.isBlank()) throw new IllegalArgumentException("bos sahip");
        if (bakiye < 0) throw new IllegalArgumentException("negatif bakiye");
        this.sahip = sahip;
        this.bakiye = bakiye;
    }

    String sahip() { return sahip; }
    long bakiye() { return bakiye; }
}

public class YapiciAtlanir {
    // Yapici hic calistirilmadan uretilmis bir bayt dizisi: sahip bos, bakiye negatif.
    static final String BOZUK_KAYIT =
            "rO0ABXNyAAVIZXNhcAAAAAAAAAABAgACSgAGYmFraXllTAAFc2FoaXB0ABJMamF2YS9sYW5nL1N0cmluZzt4cP/////////OdAAA";

    static int gecerliDegismez(Hesap h) {
        int sayi = 0;
        if (h.sahip() != null && !h.sahip().isBlank()) sayi++;
        if (h.bakiye() >= 0) sayi++;
        return sayi;
    }

    public static void main(String[] args) throws Exception {
        int yapiciEngeli = 0;
        try {
            new Hesap("", -50L);
        } catch (IllegalArgumentException e) {
            yapiciEngeli = 1;
        }
        System.out.println("yapici iki degismezi de zorluyor mu: " + (yapiciEngeli == 1));

        byte[] bayt = Base64.getDecoder().decode(BOZUK_KAYIT);
        Hesap geriGelen;
        try (ObjectInputStream i = new ObjectInputStream(new ByteArrayInputStream(bayt))) {
            geriGelen = (Hesap) i.readObject();
        }
        System.out.println("geri okuma yapiciyi hic calistirmadan basarili mi: true");
        System.out.printf("yapicinin kurdugu iki degismezden gecerli kalan: %d / 2%n",
                gecerliDegismez(geriGelen));
    }
}
```

```
yapici iki degismezi de zorluyor mu: true
geri okuma yapiciyi hic calistirmadan basarili mi: true
yapicinin kurdugu iki degismezden gecerli kalan: 0 / 2
```

İlk satır, doğrudan yapıcıdan geçmeye çalışan bozuk bir çağrının reddedildiğini doğruluyor:
denetim orada çalışıyor. İkinci satır aynı sınıfın **aynı bozukluğu** taşıyan bir kaydı
geri okuyabildiğini gösteriyor — hiçbir istisna düşmüyor, `readObject` sorunsuzca
tamamlanıyor. Üçüncü satır bunun bedelini sayıyor: yapıcının kurduğu iki değişmezden
**sıfırı** geri okumada geçerli kalıyor.

Bunun nedeni `ObjectInputStream`'in nasıl çalıştığıdır. Varsayılan geri okuma, sınıfın
alanlarına girdi-çıktı akışındaki değerleri **doğrudan** yazar; bunu yaparken hiçbir yapıcı çağırmaz, hiçbir
`if` satırından geçmez. `final` alanlar bile bu yoldan yazılabilir, çünkü dizileştirme dilin
kendi düzeneğidir ve yapıcının önündeki kapıdan girmez, nesneyi doğrudan bellekte kurar. Bu,
K02'de kurulan "bir nesne ancak yapıcısından geçerek var olabilir" kuralının **tek**
istisnasıdır — ve istisna kuralın bir zayıflığı değil, dizileştirmenin **tanımının**
kendisidir: geri okumanın işi bir yapıcı çağırmak değil, bir nesneyi aynen yeniden kurmaktır.

Bu yol tamamen kapalı da değildir: `Hesap` `Serializable`'ı hiç uygulamasaydı, `writeObject`
çağrısının kendisi `NotSerializableException` ile düşerdi ve geri okuma sorusu hiç
gündeme gelmezdi. Arayüzü konuşmak, sınıfı bu ikinci yola **açan** karardır — ve bu karar
sınıfı tasarlayanın kendisine aittir, dizileştirmenin dayattığı bir şey değildir.

K02'nin kapsülleme dersi, bir alanı `private` yapmanın onu yalnız sınıfın kendi yöntemleri
aracılığıyla değiştirilebilir kıldığını kurmuştu. Dizileştirme bu duvarı yıkmaz ama
**yanından dolaşır**: `private` değiştirici hâlâ yoktur, hiçbir sıradan çağrı `bakiye`
alanına dışarıdan doğrudan atama yapamaz — ama geri okuma sıradan bir çağrı değil, sanal
makinenin kendi düzeneğidir ve erişim belirtecine hiç takılmaz. Kapsülleme dışarıdan gelen
**yöntem çağrılarını** kısıtlar; dizileştirme bir yöntem çağrısı değil, dilin kendisine ait
bir yeniden kurma işlemidir.

## Geri Okuma Kancası Denetimi Geri Getiriyor

`ObjectInputStream`'in belgesi bir kaçış yolu tanımlar: bir sınıf, imzası tam olarak
`private void readObject(ObjectInputStream)` olan bir yöntem tanımlarsa, geri okuma
varsayılan alanı doldurma davranışını bırakıp bu yöntemi çağırır. İmza harfiyen uyulması
gereken bir sözleşmedir — yöntem `public` olursa, parametre tipi değişirse ya da adı farklı
yazılırsa geri okuma onu hiç görmez ve sessizce varsayılan davranışa döner.

- **IO19** — `Hesap`'a `readObject` adında özel bir yöntem eklenir. Bu yöntem önce
  `defaultReadObject` ile alanları her zamanki gibi doldurur, sonra yapıcıdaki **aynı iki**
  denetimi tekrarlar.

```java
// GeriOkumaKancasi.java — readObject kancasi yazildiginda iki degismez de geri kontrol ediliyor
import java.io.*;
import java.util.Base64;

class Hesap implements Serializable {
    private static final long serialVersionUID = 1L;
    private final String sahip;
    private final long bakiye;

    Hesap(String sahip, long bakiye) {
        if (sahip == null || sahip.isBlank()) throw new IllegalArgumentException("bos sahip");
        if (bakiye < 0) throw new IllegalArgumentException("negatif bakiye");
        this.sahip = sahip;
        this.bakiye = bakiye;
    }

    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
        in.defaultReadObject();
        if (sahip == null || sahip.isBlank()) throw new InvalidObjectException("bos sahip");
        if (bakiye < 0) throw new InvalidObjectException("negatif bakiye: " + bakiye);
    }
}

public class GeriOkumaKancasi {
    static final String BOZUK_KAYIT =
            "rO0ABXNyAAVIZXNhcAAAAAAAAAABAgACSgAGYmFraXllTAAFc2FoaXB0ABJMamF2YS9sYW5nL1N0cmluZzt4cP/////////OdAAA";

    public static void main(String[] args) throws Exception {
        byte[] bayt = Base64.getDecoder().decode(BOZUK_KAYIT);
        String sonuc;
        try (ObjectInputStream i = new ObjectInputStream(new ByteArrayInputStream(bayt))) {
            i.readObject();
            sonuc = "acildi";
        } catch (InvalidObjectException e) {
            sonuc = "reddedildi: " + e.getMessage();
        }
        System.out.println("kanca yazildiktan sonra ayni bozuk kayit: " + sonuc);
    }
}
```

```
kanca yazildiktan sonra ayni bozuk kayit: reddedildi: bos sahip
```

Bir önceki bölümde okunan **aynı** bayt dizisi bu kez reddediliyor. Değişen tek şey sınıfın
içine iki satırlık bir yöntem eklenmesi: `defaultReadObject` çağrısı alanları her zamanki
gibi dolduruyor, ardından gelen iki `if` yapıcıdakiyle **birebir aynı** denetimi tekrar
kuruyor. `ObjectInputStream`, bir sınıfta `readObject` adında böyle bir yöntem bulursa
varsayılan davranış yerine onu çağırıyor; yöntem bir istisna fırlatırsa geri okuma da
yarım kalıyor ve nesne hiç üretilmiyor.

Bu ölçümün söylediği şey şudur: dizileştirmenin sözü **eksik değildi**. Söz zaten "bir
nesneyi aynen yeniden kurarım, alanlarını doğrudan doldururum" biçimindeydi ve bu sözde
hiçbir değişmez denetimi yoktu — çünkü o denetim yapıcının işiydi, dizileştirmenin değil.
Denetimin geri gelmesi için gereken şey kütüphaneden bir düzeltme değil, sınıfın **kendi**
`readObject`'ini yazmasıydı; bu satırı yazmak yine çağırana, yani sınıfı tasarlayan tarafa
kalıyor.

Kursun üçüncü iddiası burada da doğrulanıyor: bu boşluğun hiçbirini derleyici yakalamıyor.
Kancasız `Hesap` de, kancalı `Hesap` de aynı sorunsuzlukla derleniyor; derleyici bir sınıfın
`readObject`'i olup olmadığına, olan `readObject`'in yapıcıdaki denetimleri tekrarlayıp
tekrarlamadığına hiç bakmıyor. `Serializable` bir işaretleyici arayüzdür — hiçbir soyut
yöntem taşımaz — ve bu yüzden derleme zamanında zorlanabilecek hiçbir şey yoktur; bütün
denetim isteğe bağlı bir yöntemin isteğe bağlı gövdesine bırakılmıştır.

## Alan Adı ve Sürüm Kimliği Bir Sözleşmedir

- **IO20** — Elde, sürüm kimliği `42` olan eski bir `Hesap` kaydı var; o zamanki sınıfta
  alanın adı `bakiye` idi. Şimdiki sınıfta aynı alan `miktar` olarak yeniden adlandırıldı,
  ama sürüm kimliği hâlâ `42`.
- **IO21** — Aynı eski kayıt, bu kez sürüm kimliği `43` olan bir sınıf tanımına karşı
  okunur — yani sürüm kimliği de değişmiş olsaydı ne olacağı ölçülüyor.

```java
// AlanAdiSozlesmesi.java — alan adi ve dizilestirme surum kimligi sozlesmeye donusuyor
import java.io.*;
import java.util.Base64;

class Hesap implements Serializable {
    private static final long serialVersionUID = 42L;
    private final String sahip;
    private final long miktar;
    Hesap(String sahip, long miktar) {
        if (miktar < 0) throw new IllegalArgumentException("negatif miktar");
        this.sahip = sahip;
        this.miktar = miktar;
    }
    long miktar() { return miktar; }
    String sahip() { return sahip; }
}

public class AlanAdiSozlesmesi {
    // Eski surumde ayni sinif "bakiye" adinda bir alan tasiyordu, "miktar" degil;
    // dizilestirme surum kimligi (42L) o zamandan bu yana degistirilmedi.
    static final String ESKI_KAYIT =
            "rO0ABXNyAAVIZXNhcAAAAAAAAAAqAgACSgAGYmFraXllTAAFc2FoaXB0ABJMamF2YS9sYW5nL1N0cmluZzt4cAAAAAAAAABkdAADYWRh";

    public static void main(String[] args) throws Exception {
        byte[] bayt = Base64.getDecoder().decode(ESKI_KAYIT);
        Hesap h;
        try (ObjectInputStream i = new ObjectInputStream(new ByteArrayInputStream(bayt))) {
            h = (Hesap) i.readObject();
        }
        System.out.println("gecti mi (istisna yok): true");
        System.out.println("sahip alani (adi degismedi): " + h.sahip());
        System.out.println("miktar alani (eskiden bakiye idi): " + h.miktar());
    }
}
```

```
gecti mi (istisna yok): true
sahip alani (adi degismedi): ada
miktar alani (eskiden bakiye idi): 0
```

Geri okuma hiç itiraz etmiyor. `sahip` alanı adı değişmediği için sorunsuz doluyor: `ada`.
Ama `miktar` alanı, akıştaki kaydın `bakiye` diye taşıdığı değeri **hiç görmüyor** — `long`
tipinin varsayılan değeriyle, sıfırla dolduruluyor. `ObjectInputStream` akıştaki her alanı
adına göre eşliyor; akışta `miktar` adında bir alan yoksa, sınıftaki `miktar` hiç
dokunulmamış sayılıyor. Sürüm kimliği aynı kaldığı için hiçbir uyarı da çıkmıyor: kütüphane
açısından bu geçerli bir geri okumadır.

```java
// SurumKimligiUyusmazligi.java — surum kimligi degistiginde ayni bayt dizisi reddedilir
import java.io.*;
import java.util.Base64;

class Hesap implements Serializable {
    private static final long serialVersionUID = 43L;
    private final String sahip;
    private final long miktar;
    Hesap(String sahip, long miktar) {
        if (miktar < 0) throw new IllegalArgumentException("negatif miktar");
        this.sahip = sahip;
        this.miktar = miktar;
    }
}

public class SurumKimligiUyusmazligi {
    static final String ESKI_KAYIT =
            "rO0ABXNyAAVIZXNhcAAAAAAAAAAqAgACSgAGYmFraXllTAAFc2FoaXB0ABJMamF2YS9sYW5nL1N0cmluZzt4cAAAAAAAAABkdAADYWRh";

    public static void main(String[] args) throws Exception {
        byte[] bayt = Base64.getDecoder().decode(ESKI_KAYIT);
        String sonuc;
        try (ObjectInputStream i = new ObjectInputStream(new ByteArrayInputStream(bayt))) {
            i.readObject();
            sonuc = "gecti";
        } catch (InvalidClassException e) {
            sonuc = "reddedildi: " + e.getClass().getSimpleName();
        }
        System.out.println("surum kimligi 43 iken ayni eski kayit: " + sonuc);
    }
}
```

```
surum kimligi 43 iken ayni eski kayit: reddedildi: InvalidClassException
```

Bu kez aynı eski kayıt daha derine bile inmiyor: sürüm kimliği eşleşmediği için geri okuma
alanlara hiç bakmadan reddediliyor. İki ölçüm yan yana konduğunda ters bir sonuç çıkıyor.
Sürüm kimliğini **değiştirmek**, uyumsuz bir kaydı gürültülü biçimde durduruyor. Sürüm
kimliğini **sabit tutmak** — sanki sınıf hiç değişmemiş gibi davranmak — yapı gerçekten
değiştiğinde sessizce yanlış bir değer üretiyor. Dizileştirme sürüm kimliği bu yüzden bir
sürüm numarası değil, "bu sınıf tanımıyla o akış birbirini tanıyor mu" sorusuna verilen
tek bitlik bir yanıttır; alan adlarının kendisi de bu yanıtın bir parçasıdır, çünkü
bağlama adla yapılır.

Sürüm kimliği hiç yazılmasaydı sonuç değişmezdi, çünkü derleyici o durumda kimliği sınıfın
o anki şekline bakarak kendisi hesaplar; iki farklı şekilden hesaplanan iki kimlik neredeyse
her zaman farklı çıkar ve sonuç yine `InvalidClassException` olurdu. Sürüm kimliğini elle ve
sabit yazmak bu otomatik korumayı bilinçli olarak kapatmaktır — geriye dönük uyumluluk
isteniyorsa gereklidir, ama isteniyorsa **her** alan değişikliğinin geri okumaya ne
yapacağını da düşünmeyi gerektirir. Aynı mantık eklenen bir alan için de geçerlidir: akışta
hiç bulunmayan yeni bir alan, tıpkı `miktar` gibi, sessizce kendi varsayılan değerini alır.

## Özet

- Dizileştirme yapıcıyı hiç çalıştırmaz; `ObjectInputStream` alanları doğrudan doldurur ve
  `final` alanlar bile bu yoldan yazılabilir.
- Yapıcının kurduğu iki değişmezden hiçbiri geri okumada kendiliğinden korunmaz: elde
  bulunan bozuk bir kayıt hiçbir istisna vermeden geri okunur.
- Sınıfa özel bir `readObject` yöntemi eklenip yapıcıdaki denetimler tekrarlandığında,
  aynı bozuk kayıt artık reddedilir; denetim geri gelir çünkü çağıran onu yazmıştır.
- Alan adı geri okumada bağlama anahtarıdır: akıştaki bir alan sınıfta bulunmuyorsa yok
  sayılır, sınıftaki bir alan akışta bulunmuyorsa varsayılan değerle sessizce doldurulur.
- Dizileştirme sürüm kimliği sabit tutulduğunda yapı değişikliği sessizce geçer; sürüm
  kimliği değiştirildiğinde aynı uyumsuzluk gürültülü biçimde reddedilir.
- `Serializable` işaretleyici bir arayüzdür ve hiçbir soyut yöntem taşımaz; bu yüzden bir
  sınıfın değişmezlerini geri okumada da koruyup korumadığını derleyici hiçbir zaman
  denetlemez — denetim tamamen isteğe bağlı bir `readObject`'in gövdesine bağlıdır.

## Sonraki Adım

Bu derste ölçülen her şey belleğe alınmış, sabit bir bayt dizisiydi — zaman hiç
karışmadı. Ama bir sistemin taşıdığı en yaygın veri türlerinden biri zamanın kendisidir.
Sıradaki ders tarih ve zaman API'sine bakar: bir anlık noktanın, bir yerel tarih-saatin ve
bir bölgesel tarih-saatin hangi bilgiyi taşıyıp hangisini taşımadığını, ve bu farkın iki
değeri karşılaştırırken nasıl sessizce yanlış bir sonuca yol açabildiğini ölçer.
