The Persistence of Memory: Object Serialization Ch 12: “Object Streams”
Administrivia M1 grade sheets back tonight Quiz (indiv/group): Mon, Feb 14 (happy V-day) Swing: Ch. 7 pp Event handling: Ch 8, pp Play with Swing a bit yourself Paper: Berkun, S., “How to build a better web browser.” Web essay, Dec In-class discussion, Mon Feb 21 See class homepage for URL, reading requirements
Group design followup Group 1 soln: 3/5 points Considered all important issues Stymied on actual solution Group 0 soln: 5/5 points Addressed all important issues Used available toolbox well
So you want to save your data... Common problem: You’ve built a large, complex object REVERSE INDEX Game state Database of student records Etc... Want to store to disk and retrieve later Crawl re-start Or: want to send over network to another program In general: want object persistence: outlive current program
Answer 1: Homebrew files You’ve got file I/O nailed, so... Write a set of methods for saving/loading each class that you care about public class MyClass { public void saveYourself(Writer o) throws IOException {... } public static MyClass loadYourself(Reader i) throws IOException {... } }
Coolnesses of Approach 1 Can produce arbitrary file formats Know exactly what you want to store and get back/don’t store unnecessary stuff Can build file formats to interface w/ other codes/programs XML Tab-delimited/spreadsheet Databases Etc. Nicely hierarchical data ⇒ load/save simple (conceptually & in code)
Recursive data structs
Saving recursive data public class MyClassA implements Saveable { public MyClassA(DataT dat) { // initialize private data members of A } public void saveYourself(Writer o) throws IOException { // write MyClassA identifier and private // data on output stream o o.flush(); } public static MyClassA loadYourself(Reader i) throws IOException { // parse MyClassA ID from i; fail if no A ID // parse MyClassA from the data stream i MyClassA tmp=new MyClassA(data); return tmp; }
Saving recursive data public class MyClassB implements Saveable { private MyClassA _Adat; public MyClassB(MyClassA a,DataT dat) {... } public void saveYourself(Writer o) { // write ID for MyClassB _Adat.saveYourself(o); // write other private data for B o.flush(); } public static MyClassB loadYourself(Reader i){ // parse MyClassB ID from i; fail if no B ID MyClassA tmpA=MyClassA.loadYourself(i); // parse MyClassB from the data stream i MyClassB tmpB=new MyClassB(tmpA,data); return tmpB; }
Saving recursive data public class MyClassC implements Saveable { private MyClassB _Bdat; public MyClassC(MyClassB b,DataT dat) {... } public void saveYourself(Writer o) { // write ID for MyClassC _Bdat.saveYourself(o); // write other private data for C o.flush(); } public static MyClassB loadYourself(Reader i){ // parse MyClassC ID from i; fail if no C ID MyClassB tmpB=MyClassB.loadYourself(i); // parse MyClassC from the data stream i MyClassC tmpC=new MyClassC(tmpb,data); return tmpC; }
Painfulness of Approach 1 This is recursive descent parsing/formatting You’ll use a recursive descent technique to handle the AND/OR language in M3, and elsewhere in this class But... It’s also a pain in the a** Tedious Error-prone What do you do about cyclic data structs? (A→B→A) If all you want to do is load/store data, do you need all of that infrastructure?
The object graph
Approach 2: Serialization Java provides the “serialization” mechanism Essentially does what we just walked through Automates the grunt work for you Gets complex cases right Good error trapping (version changes, etc.) Short form: public class MyClassA implements Serializable {... } public class MyClassB implements Serializable {... } public class MyClassC implements Serializable {... } // somewhere else in your code MyClassC localC=new MyClassC(...) FileOutputStream fos=new FileOutputStream(”dumpfile.obj”); ObjectOutputStream out=new ObjectOutputStream(fos); out.writeObject(localC); out.flush(); out.close();
A bit more detail... To (de-)serialize an object, it must implement Serializable All data members have to be Serializable also And so on, recursively... Primitive (atomic) types (int, char, etc.) are automatically Serializable So are Strings, arrays, most stuff in java.util, etc. This saves/restores entire object graph, including ensuring uniqueness of objects
Subtleties... static fields are not automatically serialized (why?) Not possible to automatically serialize them b/c they’re owned by an entire class, not a single object Options: final static fields are automatically initialized (once) when class is first loaded Possibly when first obj of that class is deserialized static fields initialized in static {} block also initialized when class first loaded What about other static data?
When default ain’t enough Java allows writeObject() and readObject() methods to customize output If a class overrides these methods, the serialization/deserialization mechanism calls them instead of doing the default thing They must (de-)serialize everything you care about: anything they skip doesn’t get written/loaded
writeObject in action public class DemoClass implements Serializable { private int _dat=3; private static char _sdat=’J’; public void writeObject(ObjectOutputStream o) throws IOException { o.writeInt(_dat); o.writeChar(_sdat); } private void readObject(ObjectInputStream i) throws IOException, ClassNotFoundException { _dat=i.readInt(); _sdat=i.readChar(); }
Stuff you don’t want to save Sometimes you want to not store some non- static data Computed vals that are cached just for convenience/speed Passwords or other “secret” data “Unique” IDs that are unique only w/in program scope Java has the transient keyword transient foo == “don’t save foo ” public class TDRLClass implements Serializable { public int _primary=3; // is serialized public transient int _cached=_primary*3; // _cached is _not_ serialized }
Gotchas #0 Non-serializable stuff What if class Foo has a member of class Bar, Foo is Serializable, but Bar isn’t? If you just do this: Foo f=new Foo(new Bar()); ObjectOutputStream o=new ObjectOutputStream o.writeObject(f); You get a NotSerializableException (bummer) Answer: use Foo.writeObject() / readObject() to write parts of Bar by hand. Bar must provide some way to get/set critical state