Java’da Serileştirme İşlemleri – 1

Java gibi nesneye yönelik programlama dillerinde, nesneye ait alanların değerleri o nesnenin durumunu oluştururken, yazdığımız metotlar ise o nesnenin davranışını belirleyen ve nesnenin durumu üzerinde değişiklikler yapan kodlardır. Java dili bizlere bir nesnenin herhangi bir andaki durumunu bir dosyaya kaydetmemizi ve daha sonra bu dosya üzerinden nesneyi tekrar oluşturmamızı sağlayan bir mekanizma sunmaktadır. Bu mekanizmaya Serileştirme denir. Serileştirmeyi kullanarak JVM içerisindeki nesneleri sabit diske kaydedip kalıcı olmalarını sağlayabiliriz veya bir yerden başka bir yere aktarabiliriz .

Serileştirme yapılırken sınıf adı, tanımlı alanların türleri, adları ve değerleri gibi bilgiler tek tek bytelara çevrilir ve bir dosyaya kaydedilir. Daha sonra bu dosya kullanılarak aynı nesne yeniden oluşturulabilir. Serileştirme genellikle nesnelerin bir ağ üzerinden gönderilmesi amacıyla kullanılır. Serileştirme işlemi uygulanarak byte dizisi haline getirilen nesne ağ üzerinden başka bir bilgisayara gönderilir ve alıcı bilgisayar üzerinde yeniden oluşturulur. Nesnenin byte dizisine çevrilmesi işlemine “Serialization”, byte dizisi kullanılarak nesnenin yeniden oluşturulmasına ise “De-Serialization” denir. Şimdi Java dilinde serileştirmenin nasıl yapıldığına bakalım.

Serializable Arayüzü ile Serileştirme Yapmak

Java kullanarak serileştirme yapmak için farklı yollar vardır. Bunlardan ilki Serializable arayüzünü kullanmaktır. Serializable arayüzünü uygulayan sınıflar serileştirilebilirler. Hemen koda geçelim.

import java.io.Serializable;

public class Ogrenci implements Serializable {

	private int ogrNo;
	private String isim;

	public Ogrenci(int ogrNo, String isim) {
		this.ogrNo = ogrNo;
		this.isim = isim;
	}

	public int getOgrNo() {
		return ogrNo;
	}

	public String getIsim() {
		return isim;
	}
}

Yukarıda int ve String türünde iki alanı bulunan ve Serializable arayüzünü uygulayan örnek bir sınıf görülmektedir. Bu sınıfta getter metotları haricinde bir metot yoktur. Yani Serializable arayüzü bizi bir metodu uygulamak zorunda bırakmaz, sadece sınıfın serileştirilebilir olduğunu belirler. Böyle arayüzlere Java dilinde “marker interface” adı verilir. Yukarıda yazdığımız sınıf Serializable arayüzünü uyguladığına göre bu sınıfı serileştirmek mümkündür. Şimdi aşağıdaki koda bakalım.

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;

public class SerializeExample {

	public static void main(String[] args) {

		Ogrenci ogrenci = new Ogrenci(1, "Seçkin Tozlu");
		ObjectOutputStream out = null;
		FileOutputStream fileOut = null;

		try {
			fileOut = new FileOutputStream("ogrenci.ser");
			out = new ObjectOutputStream(fileOut);
			out.writeObject(ogrenci);
		} catch (IOException i) {
			i.printStackTrace();
		}
		finally {
			try {
				if (out != null)
                                    out.close();
                                if (fileOut != null)
				    fileOut.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
}

Yukarıdaki kodun 9. satırında ogrNo alanı “1” isim alanı ise “Seçkin Tozlu” olan bir Ogrenci nesnesi oluşturuyoruz. Daha sonra ilk try bloğu içerisinde “ogrenci.ser” isimli bir dosya açıyoruz ve 16. satırda nesnemizi bu dosya içerisine yazıyoruz. Kod çalıştırıldıktan sonra ogrenci.ser isimli dosya içerisinde byte dizisine çevrilmiş olan nesnemiz olacaktır. Şimdi bu dosyayı kullanarak nesnemizi oluşturmaya çalışalım.

import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;

public class DeserializeExample {

	public static void main(String args[]) {

		FileInputStream fileIn = null;
		ObjectInputStream in = null;

		try {
			fileIn = new FileInputStream("ogrenci.ser");
			in = new ObjectInputStream(fileIn);

			Ogrenci ogrenci = (Ogrenci) in.readObject();

			System.out.println("Dosyadan okunan ogrenci nesnesi: "
					+ ogrenci.getOgrNo() + " " + ogrenci.getIsim());

		} catch (IOException e) {
			e.printStackTrace();
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		} finally {
			try {
                                if (in != null)
				    in.close();
				if (fileIn != null)
                                    fileIn.close();
			} catch (IOException e1) {
				e1.printStackTrace();
			}
		}
	}
}

Nesnemizi “ogrenci.ser” isimli dosyaya kaydetmiştik. try bloğu içerisinde bu dosyayı açtıktan sonra 16. satırda nesneyi okuyup Ogrenci sınıfına cast ediyoruz. Dikkat ettiyseniz burada kendimiz bir öğrenci nesnesi oluşturmuyoruz, JVM dosyadan okuduğu bilgilerle nesneyi oluşturup bize sunuyor, biz de kullanıyoruz. Daha sonra dosyadan okuyarak oluşturduğumuz nesneye ait bilgileri ekrana yazdırarak doğruluğunu kontrol ediyoruz. Kodun çıktısı aşağıdaki gibi olacaktır.

Dosyadan okunan ogrenci nesnesi: 1 Seçkin Tozlu

Gördüğünüz gibi ilk başta oluşturduğumuz nesneyi aynen elde ettik. Serileştirme işlemi sayesinde bir nesneyi bir yerden bir yere taşımak, nesnenin saklanmasını sağlamak mümkündür.

transient Anahtar Kelimesi

Nesne serileştirilirken Serializable arayüzü kullanıldığında, o nesneye ait bütün alanlar için serileştirme işlemi uygulanır. Ancak transient anahtar kelimesini kullanarak nesneye ait bazı alanların serileştirilmesini engelleyebiliriz. Örneğin bir sınıf içerisinde bir kullanıcının şifresi tutuluyorsa, biz nesneyi serileştirdikten sonra ağ üzerinden gönderim yaparken ağı dinleyen bir kişi kullanıcının şifresine erişebilir. Bunun gibi sakıncalı durumları ortadan kaldırmak için bazı alanları serileştirme işleminin dışında tutmak gerekebilir. Aşağıdaki kodu inceleyelim.

import java.io.Serializable;

public class Ogrenci implements Serializable {

	private int ogrNo;
	private transient String isim;

	public Ogrenci(int ogrNo, String isim) {
		this.ogrNo = ogrNo;
		this.isim = isim;
	}

	public int getOgrNo() {
		return ogrNo;
	}

	public String getIsim() {
		return isim;
	}
}

Öğrenci sınıfını içerisindeki isim alanını transient olarak tanımlayarak serileştirme işleminin dışında tutmuş oluyoruz. Öğrenci sınıfını bu şekilde değiştirdikten sonra yukarıda yaptığımız serileştirme işlemlerini yeniden yaparsak, yani önce nesneyi dosyaya kaydedip daha sonra dosyayı okuyarak nesneyi yeniden oluşturmaya çalışırsak aşağıdaki gibi bir çıktı alırız.

Dosyadan okunan ogrenci nesnesi: 1 null

Normalde nesneyi oluştururken isim alanına “Seçkin Tozlu” değerini atamıştık ancak serileştirme işleminden sonra elde ettiğimiz nesnede bu değeri göremedik. Çünkü isim alanı transient olarak tanımlandığı için serileştirme işlemine katılmadı.

Externalizable Arayüzü ile Serileştirme Yapmak

Serileştirme işlemi Serializable arayüzünün dışında Externalizable arayüzü kullanılarak da yapılabilir. Externalizable arayüzünü kullanmak serileştirme işlemini kendimize göre özelleştirmemizi sağlar ancak bu durumda daha çok kod yazmamız gerekecektir. Aşağıdaki örneğe bakalım.

import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;

public class Ogrenci2 implements Externalizable {

	private int ogrNo;
	private String isim;

	public Ogrenci2() {}

	public Ogrenci2(int ogrNo, String isim) {
		this.ogrNo = ogrNo;
		this.isim = isim;
	}

	public int getOgrNo() {
		return ogrNo;
	}

	public String getIsim() {
		return isim;
	}

	@Override
	public void readExternal(ObjectInput in) throws IOException,ClassNotFoundException {
		this.ogrNo = in.readInt();
		this.isim = (String)in.readObject();
	}

	@Override
	public void writeExternal(ObjectOutput out) throws IOException {
		out.writeInt(ogrNo);
		out.writeObject(isim);
	}
}

Yeni yazdığımız Ogrenci2 sınıfı Externalizable arayüzünü kullanmaktadır. Bu arayüz Serializable arayüzünün aksine, bizden writeExternal() ve readExternal() metotlarını uygulamamızı istemektedir. writeExternal() metodu nesne serileştirilirken, readExternal() ise nesne yeniden oluşturulurken kullanılır. Bu metotları kullanmak bize nesneyi serileştirirken büyük esneklik sağlar. İstediğimiz alanları serileştirmeye dahil edebilir, istediklerimizi çıkarabiliriz. Hatta güvenlik kaygılarımız varsa nesneyi serileştirirken şifreleyebiliriz. Dikkat etmemiz gereken nokta, serileştirme yaparken nesneye ait alanları hangi sırayla yazdıksak, okurken de aynı sırada okumalıyız. Şimdi Externalizable arayüzü kullanıldığında nesnenin nasıl serileştirildiğini görmek için aşağıdaki koda bakalım.

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;

public class Serialize2 {

	public static void main(String[] args) {

		Ogrenci2 ogrenci = new Ogrenci2(1, "Seçkin Tozlu");
		ObjectOutputStream out = null;
		FileOutputStream fileOut = null;

		try {
			fileOut = new FileOutputStream("ogrenci2.ser");
			out = new ObjectOutputStream(fileOut);
			ogrenci.writeExternal(out);

		} catch (IOException i) {
			i.printStackTrace();
		}
		finally {
			try {
				if(out != null)
					out.close();
				if(fileOut != null)
					fileOut.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
}

Serileştirilecek nesnenin Externalizable arayüzünü uyguladığı durumda yukarıdaki gibi bir kod nesnemizi serileştirmeyi sağlayacaktır. Öğrenci nesnesini oluşturduktan sonra 16. satırda, oluşturduğumuz nesne üzerinden writeExternal() metodunu çağırarak nesneye ait alanların ogrenci2.ser dosyası içerisine yazılmasını sağlıyoruz. Şimdi de ogrenci2.ser dosyasını kullanarak nesneyi oluşturmaya çalışalım.

import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;

public class Deserialize2 {
	public static void main(String args[]) {

		FileInputStream fileIn = null;
		ObjectInputStream in = null;

		try {
			fileIn = new FileInputStream("ogrenci2.ser");
			in = new ObjectInputStream(fileIn);

			Ogrenci2 ogrenci = new Ogrenci2();
			ogrenci.readExternal(in);

			System.out.println("Dosyadan okunan ogrenci2 nesnesi: "
					+ ogrenci.getOgrNo() + " " + ogrenci.getIsim());

		} catch (IOException e) {
			e.printStackTrace();
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		} finally {
			try {
				if(in != null)
					in.close();
				if(fileIn != null)
					fileIn.close();
			} catch (IOException e1) {
				e1.printStackTrace();
			}
		}
	}
}

ogrenci2.ser dosyasına kaydedilmiş olan nesneyi JVM ortamında yeniden oluşturabilmek için yukarıdaki gibi bir kod yazabiliriz. 15. satırda nesneyi oluşturuyoruz ve oluşturduğumuz nesne referansı üzerinde readExternal() metodunu çağırıyoruz. Böylece nesnemizin alanlarına dosyadan okunan değerleri atamış oluyoruz. Burada dikkatimizi çekmesi gereken nokta şudur: Serializable arayüzünü uyguladığımızda nesneyi okurken ObjectInputStream referansı üzerinden readObject() metodunu çağırıyorduk ve dönen Object nesnesini kendi sınıfımıza cast ederek kullanıyorduk. Kendimiz bir nesne oluşturmuyorduk. Burada ise serileştirilen nesneyi kendimiz oluşturup bu nesne üzerinden readExternal() metodunu çağırıyoruz.

Externalizable arayüzünü kullandığımız zaman oluşturduğumuz dosyanın boyutu daha küçük olacaktır. Çünkü Externalizable arayüzü kullanıldığında dosyaya sadece alanlara karşılık gelen veriler yazılırken, Serializable arayüzünü kullandığımız zaman verilere ek olarak sınıfa ait bir takım bilgiler ve nesneye ait alanların isimleri de yazılmaktadır. Bu yüzden Externalizable arayüzü kullanılarak serileştirme yapıldığında oluşan dosya daha çabuk aktarılabilir.

Serileştirme yaptığımız sınıf içerisinde başka sınıflara ait referanslar varsa, bu sınıfların da serileştirilebilir bir sınıf olması gerekmektedir. Ogrenci sınıfımızda yer alan String nesneleri problem çıkarmadı çünkü String sınıfı Serializable arayüzünü uygulamaktadır. Ogrenci sınıfı içerisinde, serileştirilemeyen bir sınıfa ait bir referans olsaydı serileştirme işlemi yarıda kalacak ve NotSerializableException fırlatılacaktı. Bu sebeple serileştirmek istediğimiz sınıf içerisinde başka sınıflara ait referanslar varsa, bunları serileştirme işleminin dışında tutmalıyız ya da onların da serileştirilebilir bir sınıf olmasına dikkat etmeliyiz.

Serileştirme yapılırken durumu kaydedilen nesnenin aynı şekilde eksiksiz olarak geri elde edilebilmesi için Java Object Serialization Specification adında bir standart belirlenmiştir. Tam olarak nesneye ait hangi verilerin ne formatta yazıldığını byte byte görüp incelemek isteyen arkadaşlar bu linke tıklayarak Object Serialization Stream Protocol’ü inceleyebilirler. Herkese kolay gelsin..

Share

3 Replies to “Java’da Serileştirme İşlemleri – 1”

  1. @Override
    public void readExternal(ObjectInput in) throws IOException,ClassNotFoundException {
    this.ogrNo = in.readInt();
    this.isim = (String)in.readObject();
    }
    Kod okunabilirliğine önem verdiğinizi gözlemledim. Sanırım bu yüzden this keyword burada kullanılması yararlı olacaktır. Ayrıca makaleleriniz çok güzel. Çok teşekkürler.

Bir Cevap Yazın