Serialization

From Suhrid.net Wiki
Jump to navigationJump to search

Intro

  • Mechanism to persist state of objects
  • ObjectOutputStream.writeObject() - serialize and write
  • ObjectInputStream.readObject() - read and deserialize
  • Object and its complete object graph being seralized must implement the Serializable interface.
  • If any object needs to be skipped from the serialization process - mark it as transient.
  • In below example, Thing is not serializable, so when a Thing is used as a field in Majig the field is marked as transient.
class Thing {
	
	private String name;
	
	public Thing(String name) {
		this.name = name;
	}
	
	public void setName(String name) {
		this.name = name;
	}
	
	public String getName() {
		return name;
	}
	
}

class Majig implements Serializable {
	private int id;
	
	private transient Thing t;
	
	public int getID() {
		return id;
	}
	
	Majig(int id, Thing t) {
		this.id = id;
		this.t = t;
	}
}

public class Ser1 {

	public static void main(String[] args) {
		
		Majig m1 = new Majig(1, new Thing("T1"));
		Majig m2 = new Majig(2, new Thing("T2"));
		
		File savFile = new File("majig.sav");
		
		try {
			ObjectOutputStream os = new ObjectOutputStream(new FileOutputStream(savFile));
			
			os.writeObject(m1);
			os.writeObject(m2);
			
			System.out.println("Serialized m1 and m2");
			
			os.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

}

writeObject and readObject

  • When serialized objects are deserialized, what happens to the transient fields ? They will be null.
  • writeObject and readObject are callback methods that we can define in the code which offer a way to save some part of the object's state manually.
  • This is the contract which must be followed: The methods must be defined in the class which we are trying to serialize.
private void writeObject(ObjectOutputStream os) {
}

private void readObject(ObjectInputStream is) {
}
  • For e.g. in the above case since Thing is transient, we can save Thing's name field instead during serialization and rebuild Thing object using the name during deserialization.
  • Before we save/retrieve our custom field, os.defaultWriteObject() and defaultReadObject() must be called to handle the normal serialization process.
import java.io.*;

class Thing {
	
	private String name;
	
	public Thing(String name) {
		this.name = name;
	}
	
	public void setName(String name) {
		this.name = name;
	}
	
	public String getName() {
		return name;
	}
	
	public String toString() {
		return getName();
	}
	
}

class Majig implements Serializable {
	private int id;
	
	private transient Thing t;
	
	public int getID() {
		return id;
	}
	
	public Thing getThing() {
		return t;
	}
	
	Majig(int id, Thing t) {
		this.id = id;
		this.t = t;
	}
	
	private void writeObject(ObjectOutputStream os) {
		try {
			os.defaultWriteObject();
			os.writeUTF(t.getName());
		} catch (IOException ioe) {
			ioe.printStackTrace();
		}
	}
	
	private void readObject(ObjectInputStream is) {
		try {
			is.defaultReadObject();
			t = new Thing(is.readUTF());
		} catch (Exception ioe) {
			ioe.printStackTrace();
		}
	}
	
}

public class Ser1 {

	public static void main(String[] args) {
		
		Majig m1 = new Majig(1, new Thing("T1"));
		Majig m2 = new Majig(2, new Thing("T2"));
		
		File savFile = new File("majig.sav");
		
		try {
			ObjectOutputStream os = new ObjectOutputStream(new FileOutputStream(savFile));
			
			os.writeObject(m1);
			os.writeObject(m2);
			
			System.out.println("Serialized m1 and m2");
			
			os.close();
			
			ObjectInputStream is = new ObjectInputStream(new FileInputStream(savFile));
			
			Majig newm1 = (Majig) is.readObject();
			Majig newm2 = (Majig) is.readObject();
			
			System.out.println("Deserialized m1 and m2");
			
			System.out.println(newm1.getID() + ", " + newm1.getThing());
			System.out.println(newm2.getID() + ", " + newm2.getThing());
			
			is.close();
			
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

}


Inheritance and Serialization

  • During deser, constructor does not run and instance initializers are not run.
  • However every non-serializable superclass's constructor will run !
  • So a non-serializable superclass constructor can overrwrite an instance variable's value if it runs!
import java.io.*;

class Gadget {
	
	int val = 10;
	
	protected int getVal() {
		return val;
	}
	
	protected void setVal(int val) {
		this.val = val;
	}
	
	Gadget() {
		System.out.println("Gadget Constructor");
	}
}

class Phone extends Gadget implements Serializable {
	
	Phone() {
		System.out.println("Phone Constructor");
	}
	
}

public class Ser2 {

	public static void main(String[] args) {
		
		try {
			
			File savFile = new File("phone.sav");
			ObjectOutputStream os = new ObjectOutputStream(new FileOutputStream(savFile));
			Phone op = new Phone();
			op.setVal(30);
			os.writeObject(op);
			System.out.println("Serialized Phone with val :" + op.getVal());
			os.close();
			
			ObjectInputStream is = new ObjectInputStream(new FileInputStream(savFile));
			Phone p = (Phone) is.readObject();
			System.out.println("Deserialized Phone");
			System.out.println("Phone Val : " + p.getVal());
			is.close();
			
		} catch (Exception e) {
			e.printStackTrace();
		}

	}

}

/*

Output : Note Phone Constructor does not run during deser, but Gadget does thus overrwriting val

Gadget Constructor
Phone Constructor
Serialized Phone with val :30
Gadget Constructor
Deserialized Phone
Phone Val : 10

*/