Eğitim Portalı/Java/Serialization (Serileştirme)
Java01-java/68-serialization

Serialization (Serileştirme)

Serialization, bir Java nesnesini byte dizisine çevirme; deserialization ise byte dizisini geri nesneye dönüştürme işlemidir. Bu sayede bir nesneyi diske kaydedebilir, ağ üzerinden gönderebilir veya bir oturumda sakla…

Serialization (Serileştirme)

Serialization, bir Java nesnesini byte dizisine çevirme; deserialization ise byte dizisini geri nesneye dönüştürme işlemidir. Bu sayede bir nesneyi diske kaydedebilir, ağ üzerinden gönderebilir veya bir oturumda saklayabilirsin. Java'nın yerleşik serileştirmesi kolaydır ama modern uygulamalarda yerini büyük ölçüde JSON gibi taşınabilir formatlara bırakmıştır — bu konuda hem nasıl çalıştığını hem de neden dikkatli kullanılması gerektiğini ele alıyoruz.

Serializable: işaretleyici arayüz

Bir nesnenin serileştirilebilmesi için sınıfı Serializable arayüzünü uygulamalıdır. Bu bir "işaretleyici" (marker) arayüzdür — metodu yoktur, sadece "bu sınıf serileştirilebilir" der:

class Kullanici implements Serializable { ... }

// Serialize: nesne -> byte[]
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(kullanici);

// Deserialize: byte[] -> nesne
ObjectInputStream ois = new ObjectInputStream(bis);
Kullanici k = (Kullanici) ois.readObject();

Örnek 1 (./Ornek1.java) bir nesneyi ByteArrayOutputStream'e serileştirip geri okur.

Bir sınıfın tüm (transient olmayan) alanları da serileştirilebilir olmalıdır; değilse NotSerializableException alınır.

transient: serileştirmeyi atla

transient anahtar kelimesi, bir alanın serileştirilmemesini sağlar. Parolalar, önbellekler, türetilmiş/geçici veriler veya serileştirilemeyen kaynaklar (bağlantılar) için kullanılır:

private transient String parola;   // serileşmez; deserialize sonrası null olur

Örnek 1'de parola transient'tir; geri yüklendiğinde null gelir.

serialVersionUID

Her serileştirilebilir sınıfın bir sürüm kimliği vardır. Veri okunurken, serileşmiş verideki UID ile sınıfın güncel UID'si karşılaştırılır; uyuşmazsa InvalidClassException atılır.

private static final long serialVersionUID = 1L;

Önemli: Bu alanı elle tanımla. Tanımlamazsan derleyici otomatik üretir; ama sınıfta en küçük değişiklik (yeni metot bile) bu otomatik değeri değiştirebilir ve eski serileşmiş veriler okunamaz hale gelir. Elle sabit bir UID, sürümler arası uyumu kontrol etmeni sağlar.

İç içe nesneler ve koleksiyonlar

Bir nesnenin içindeki nesneler ve koleksiyonlar otomatik olarak serileştirilir (graf halinde), yeter ki hepsi Serializable olsun. Örnek 2 (./Ornek2.java) iç içe Adres ve bir List içeren bir Siparis'i serileştirip geri yükler; serialVersionUID'i gösterir.

Neden modern projelerde dikkatli kullanılır?

Java'nın yerleşik serileştirmesinin ciddi sorunları vardır:

  • Güvenlik: Güvenilmeyen kaynaktan gelen veriyi deserialize etmek, ünlü "deserialization" açıklarına yol açabilir (kötü amaçlı nesne grafiği → kod çalıştırma).
  • Kırılganlık: Sınıf değişiklikleri eski verileri okunamaz yapabilir.
  • Taşınabilirlik yok: Yalnızca Java okur; başka diller/sistemler anlayamaz.

Modern alternatifler: Servisler arası ve depolamada genelde JSON (Jackson/Gson) veya Protobuf/Avro tercih edilir. Bunlar taşınabilir, sürüm-dostu ve daha güvenlidir. (Bu portalda Spring tarafında verileri JSON ile taşıyoruz.) Java serileştirmesini yalnızca tam kontrolün olduğu, JVM-içi senaryolarda (örn. bazı önbellek/oturum durumları) kullan.

Özet

Serileştirmenin nesneyi byte'lara çevirip geri kurma olduğunu; Serializable, ObjectOutputStream/ ObjectInputStream ve transient'i (Örnek 1); serialVersionUID'in sürüm uyumundaki rolünü ve iç içe nesne/koleksiyon serileştirmesini (Örnek 2) öğrendik; güvenlik/kırılganlık nedeniyle modern projelerde neden JSON/Protobuf tercih edildiğini gördük. Sırada, kendini çağıran metotlar: Recursion (özyineleme).

Kod Örnekleri(2)

Ornek1

çalıştırılabilir
Ornek1.java
1// Ornek1: Serialization — nesneyi byte'lara çevirip geri kurma; transient.
2// Çalıştırma: java Ornek1.java
3import java.io.ByteArrayInputStream;
4import java.io.ByteArrayOutputStream;
5import java.io.ObjectInputStream;
6import java.io.ObjectOutputStream;
7import java.io.Serializable;
8
9public class Ornek1 {
10
11    // Serileştirilebilir bir sınıf: Serializable işaretleyici (marker) arayüzü.
12    static class Kullanici implements Serializable {
13        private final String ad;
14        private final int yas;
15        private transient String parola; // transient -> serileştirilMEZ (gizli/geçici)
16
17        Kullanici(String ad, int yas, String parola) { this.ad = ad; this.yas = yas; this.parola = parola; }
18        @Override public String toString() { return "Kullanici{ad=" + ad + ", yas=" + yas + ", parola=" + parola + "}"; }
19    }
20
21    public static void main(String[] args) throws Exception {
22        Kullanici orijinal = new Kullanici("Ada", 30, "gizli123");
23        System.out.println("Orijinal : " + orijinal);
24
25        // SERIALIZE: nesne -> byte[] (dosyaya/ağa yazılabilir)
26        byte[] veri;
27        try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
28             ObjectOutputStream oos = new ObjectOutputStream(bos)) {
29            oos.writeObject(orijinal);
30            veri = bos.toByteArray();
31        }
32        System.out.println("Serileşmiş boyut: " + veri.length + " byte");
33
34        // DESERIALIZE: byte[] -> nesne
35        Kullanici geri;
36        try (ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(veri))) {
37            geri = (Kullanici) ois.readObject();
38        }
39        System.out.println("Geri yüklenen: " + geri);
40        System.out.println("Parola neden null? -> transient alan serileştirilmedi.");
41
42        System.out.println("""
43
44                --- Serialization (serileştirme) ---
45                Bir nesneyi byte dizisine çevirme (serialize) ve geri kurma (deserialize) işlemidir.
46                Şart: sınıf 'implements Serializable' olmalı (içindeki alanlar da serileştirilebilir olmalı).
47                transient: bu alan serileştirilMEZ (parola, önbellek, türetilmiş veri gibi geçici/gizli alanlar).
48                Kullanım: nesneyi dosyaya kaydetme, ağ üzerinden gönderme, oturum (session) saklama.""");
49    }
50}
Çıktı yerel JDK 21 ile yakalandı — tarayıcıda JVM çalışmaz.

Ornek2

çalıştırılabilir
Ornek2.java
1// Ornek2: serialVersionUID, iç içe nesneler/koleksiyonlar ve sürüm uyumu.
2// Çalıştırma: java Ornek2.java
3import java.io.ByteArrayInputStream;
4import java.io.ByteArrayOutputStream;
5import java.io.ObjectInputStream;
6import java.io.ObjectOutputStream;
7import java.io.Serializable;
8import java.util.List;
9
10public class Ornek2 {
11
12    static class Adres implements Serializable {
13        private static final long serialVersionUID = 1L;
14        final String sehir;
15        Adres(String sehir) { this.sehir = sehir; }
16        @Override public String toString() { return sehir; }
17    }
18
19    static class Siparis implements Serializable {
20        // serialVersionUID: sınıfın "sürüm kimliği". Serileşmiş veriyle sınıfın uyumunu denetler.
21        private static final long serialVersionUID = 1L;
22        final String no;
23        final List<String> urunler;   // koleksiyonlar da serileştirilebilir (içerikleri uygunsa)
24        final Adres teslimat;          // iç içe nesne de serileştirilir (o da Serializable olmalı)
25        Siparis(String no, List<String> urunler, Adres teslimat) {
26            this.no = no; this.urunler = urunler; this.teslimat = teslimat;
27        }
28        @Override public String toString() { return "Sipariş " + no + " " + urunler + " -> " + teslimat; }
29    }
30
31    public static void main(String[] args) throws Exception {
32        Siparis s = new Siparis("SP-100", List.of("Klavye", "Mouse"), new Adres("İzmir"));
33        System.out.println("Orijinal: " + s);
34
35        byte[] veri = serialize(s);
36        Siparis geri = (Siparis) deserialize(veri);
37        System.out.println("Geri yüklenen: " + geri);
38        System.out.println("İç içe nesne ve liste korundu mu? "
39                + (geri.teslimat.sehir.equals("İzmir") && geri.urunler.size() == 2));
40
41        System.out.println("""
42
43                --- serialVersionUID ve sürüm uyumu ---
44                serialVersionUID: sınıfın sürüm kimliği. Serileşmiş veri okunurken sınıfın UID'si ile
45                  karşılaştırılır; UYMAZSA InvalidClassException atılır. ELLE tanımla (yoksa derleyici
46                  otomatik üretir ve en küçük değişiklikte değişir -> eski veriler okunamaz hale gelir).
47                İç içe nesneler ve koleksiyonlar otomatik serileştirilir (hepsi Serializable olmalı).
48                MODERN ALTERNATİF: Java serileştirme güvenlik açıklarına ve sürüm kırılganlığına açıktır;
49                  servisler/depolama arası genelde JSON (Jackson) veya Protobuf tercih edilir.""");
50    }
51
52    static byte[] serialize(Object o) throws Exception {
53        try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
54             ObjectOutputStream oos = new ObjectOutputStream(bos)) {
55            oos.writeObject(o); return bos.toByteArray();
56        }
57    }
58    static Object deserialize(byte[] veri) throws Exception {
59        try (ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(veri))) {
60            return ois.readObject();
61        }
62    }
63}
Çıktı yerel JDK 21 ile yakalandı — tarayıcıda JVM çalışmaz.