20 Haziran 2019 Perşembe

Binary Serialization

Giriş
Şu satırı dahil ederiz.
mport java.io.Serializable;
Nasıl Kullanılır
Java'nın kendi içinde serialization yapısı mevcut. Aynı C#'ta olduğu gibi serialize edilecek nesnenin Serializable arayüzünden türemesi gerekiyor. Bu arayüz bir marker interface. Herhangi bir metod içermez.
class MyClass implements Serializable
{
  private static final long serialVersionUID = 12345L;
}
Sınıfı Serializable Yapmadan Önce
İyi düşünmek lazım. Açıklaması şöyle.
"Library designers must think very carefully before publishing a serializable class --- as doing so potentially commits you to maintain compatibility with all the instances that have ever been serialized."
Eleştiriler
Serialization mekanizması için bazı eleştiriler var. Bir tanesi  serialization kodunun içinin bilinmemesi. Açıklaması şöyle. Yani kod magic olarak çalışıyor.
"..the choice to implement serialization by 'magic' rather than giving deconstruction and reconstruction a first-class place in the object model itself."
Bu yüzden, sınıfın constructor,setter(),getter() gibi metodları çağrılmıyor. Açıklaması şöyle.
"Serialization pretends to be a library feature. ... In reality, though, serialization extracts object state and recreate objects via privileged, extralinguistic mechanisms, bypassing constructors and ignoring class and field accessibility."
Exception
Eğer sınıf içinde Serializable arayüzünü gerçekleştirmeyen alanlar varsa şöyle bir exception alırız.
Caused by: java.io.NotSerializableException: java.io.FileOutputStream
Bu durumda ya o alan Serializable arayüzünü gerçekleştirmeli ya da transient olarak işaretlenmeli.

serialVersionUID Alanı
serialVersionUID Alanı yazısına taşıdım.

Kendi Sınıfımdaki static ve transient Alanlar
Bu alanlar serialization işlemine girmezler. Yani yazılıp okunmazlar.

Alanları transient olarak işaretlemek için şöyle yaparız.
private transient FileInputStream fis ; 
private transient FileOutputStream fos ; 
 İstisnai tek bir durum var.
There is just one exception to this rule, and it is when the transient final field member is initialized to a constant expression as those defined in the JLS 15.28. Hence, field members declared this way would hold their constant value expression even after deserializing the object.
Constant expression'dan kasıt ise
A constant expression is an expression denoting a value of primitive type or a String
Yani aşağıdaki gibi tanımlı primitive a aslında serialization işlemine giriyor.
transient final int a = 10;
transient Alanları deserialize işleminden sonra tekrar ilklendirmek gerekir.

Ata Sınıfıtaki Alanlar
Eğer ata sınıf Serializable değilse alanları yazılıp okunmaz. Açıklaması şöyle
... the defaultWriteObject can only write the non-static and non-transient fields of the current class. Once the superclass does not implements the Serializable interface, the fields in the superclass can not be serialized into the stream

Serializable Arayüzünün Davranışını Değiştirmek - Custom Serialization
Binary Custom Serialization yazısına taşıdım.

ObjectStream Bu Metodları Nasıl Çağırır
ObjectStream sınıfı yansıma (reflection) vasıtasıyla bu metodları çağırır.
...
private Method writeObjectMethod;
private Method readObjectMethod;
private Method readObjectNoDataMethod;

...

writeObjectMethod = getPrivateMethod(cl, "writeObject",
                            new Class<?>[] { ObjectOutputStream.class },
                            Void.TYPE);
readObjectMethod = getPrivateMethod(cl, "readObject",
                            new Class<?>[] { ObjectInputStream.class },
                            Void.TYPE);
readObjectNoDataMethod = getPrivateMethod(
                            cl, "readObjectNoData", null, Void.TYPE);
...
No-Arg Constructor Gerekir mi ?
Bu hep kafa karıştıran bir soru. Bazı cevaplar aşağıda

1. Kalıtım Yoksa Kendi Sınıfıma No-Arg Constructor Gerekir mi ?
Serialize edilen nesne kalıtmıyorsa, no-arg constructor  gerekmez.

2. Kalıtım Varsa Hangi Sınıfa No-Arg Constructor Gerekir?
Eğer kalıtım varsa, hiyerarşide Serializable arayüzünü gerçekleştirmeyen ilk ata sınıfın no-arg constructor metoduna sahip olması gerekir. Sebep olarak ta kalıtılan field'ları ilklendirmek için no-arg constructor'ının çağırılması gösterilmiş. Açıklaması şöyle
During deserialization, the fields of non-serializable classes will be initialized using the public or protected no-arg constructor of the class. A no-arg constructor must be accessible to the subclass that is serializable. The fields of serializable subclasses will be restored from the stream.
Örneğin nesnemiz direkt Object'ten türüyorsa, Object Serializable arayüzünü gerçekleştirmez ancak no-arg constructor metoduna sahip olduğu için sorun çıkmaz.

3. Deserialize İşleminden Sonra Kendi Nesnemin No-Arg Constructor'ı Çağırılır mı?
Deserialize işleminden sonra kendi nesnemin no-arg constructor metodu çağırılmaz. Ancak yukarıdaki madded belirtildiği gibi ata sınıflarımdan Serializable olmayan nesnenin no-arg constructor metodu çağrılır. Çağırılması isteniyorsa aşağıdaki Serialization Proxy kullanılmalıdır.

Örnek
Elimizde şöyle bir kod olsun. Bar sınıfını i = 20 ile ilklendirip kaydedelim. Daha sonra okuma yapınca i = 10 sonucunu alırız. Çünkü Foo sınıfının no-arg constructor metodu çağrılır ve i 10 yapılır
public class Foo {

  private int i;
  Foo(){
    i=10;
  }
  public void setVal(int i){this.i = i;}
}

class Bar extends Foo implements Serializable {

  private String s;
  private Foo foo;

  Bar(String s) {
    this.s = s;
    setVal(20);
  }
  ...
}
Serialization Proxy Nedir?
Serialization Proxy yazısına taşıdım

Hiç yorum yok:

Yorum Gönder