Revisiting J2ME Object Serialization

A couple of weeks ago I received an email from someone who had read my J2ME Tech Tip Object Serialization in CLDC-based Profiles, written in 2002. As most of you probably know, object serialization — persisting the state of an object so that you can recreate the object later — is one of the things missing from the CLDC. Given that most devices these days are CLDC-based (with MIDP support), that tech tip was meant to solve a common problem that developers face when writing Java ME applications that need to store or communicate their state.

The basic idea is quite simple: define a common interface (Persistent) with methods that are invoked whenever the application wants the object to save or restore its state. It’s not automatic by any means, because the creator of the class has to write the code that does the saving (persist) and the restoring (resurrect). But with our trusty friends DataInputStream and DataOutputStream it’s pretty easy to do and with some forethought (put a version number at the start of the serialization stream) you can even write persistence code that is upwardly compatible.

Restoring the object is not as elegant as it could be, however. This is because we can only create objects via the newInstance method of the Class object. So what we do is create a prototype object using the null constructor (which, due to the lack of reflection, is the only one available to us) and then invoke resurrect to initialize the new object’s state.

This particular reader was having problems with the speed of the object restoration in my VectorHelper class. VectorHelper is a convenience class used to persist and restore Vector objects, as long as the Vector contained nothing but strings, integers or objects that implement Persistent.

Here’s the restoration code in question:

   public static Vector resurrect( byte[] persisted )
                            throws IOException {
        ByteArrayInputStream bin =
                    new ByteArrayInputStream( persisted );
        DataInputStream      din =
                    new DataInputStream( bin );

        Vector v = new Vector();
        int n = din.readInt();

        for( int i = 0; i < n; ++i ){
            int type = din.readByte();
            if( type == NULL ){
                v.addElement( null );
            } else if( type == INTEGER ){
                v.addElement( new Integer( din.readInt() ) );
            } else if( type == STRING ){
                v.addElement( din.readUTF() );
            } else if( type == PERSISTENT ){
                String cname = din.readUTF();
                int    len = din.readInt();
                byte[] tmp = new byte[ len ];

                din.readFully( tmp );

                try {
                    Class cl = Class.forName( cname );
                    Object o = cl.newInstance();
                    ((Persistent) o).resurrect( tmp );
                    v.addElement( o );
                }
                catch( IOException e ){
                    throw e;
                }
                catch( Exception e ){
                    throw new IOException( "Exception " +
                        e.toString() );
                }
            } else {
                throw new IOException( "Unknown " +
                   "type " + type );
            }
        }

        return v;
    }

Can you guess where the bottleneck might be?

It’s the code that restores Persistent objects:

    Class cl = Class.forName( cname );
    Object o = cl.newInstance();
    ((Persistent) o).resurrect( tmp );
    v.addElement( o );

The problem is that dynamically looking up a class (Class.forName) and/or instantiating it (cl.newInstance) is very slow on some devices. The solution for my reader was to speed up this process by removing the generic class lookup and instantiation code and replacing it with code specific to his application. For example, say you had two classes Employee and Student that both implemented Persistent. Then you could code:

    Object o;
    if( cname.equals( "com.mypackage.Employee" ) ){
        o = new Employee();
    } else if( cname.equals( "com.mypackage.Student" ) ){
        o = new Student();
    } else {
        throw new UnknownClassException(); // oops!
    }
    ((Persistent) o).resurrect( tmp );
    v.addElement( o );

And suddenly the restoration code is much faster.

The whole point of this example is that Java ME programming often boils down to writing specialized code instead of writing generic code. The examples in my J2ME Tech Tips and other articles tend to be generic because they need to work over a wide variety of platforms. And writing generic code often feels like the “right” or “correct” thing to do. But correctness in programming isn’t about conforming to abstract notions of what’s good and what’s bad, it’s about writing code that works and meets user expectations. This is particularly true with Java ME programming. You can get away with a lot of fat on desktop or server Java — you just run it on a faster machine or give the VM more memory. You can’t do that with Java ME applications.

Somedays when I’m feeling prickly I think that all Java programmers should learn Java on an old Motorola or Nokia phone with a 50K limit to the size of the application. Because it really forces you to think carefully about your algorithms and how you’re using memory. And in the end I think it makes you a better programmer. If I had to choose between hiring someone who’d learned Java by doing J2ME programming versus someone who’d learned it doing J2EE programming, I’d hire the J2ME coder.

Any Java coder can learn new APIs, but learning the mindset of efficient and effective Java coding is much harder to do if it’s not ingrained from day one.


Technorati Tags: , , , , , ,

Share and Enjoy:These icons link to social bookmarking sites where readers can share and discover new web pages.
  • blinkbits
  • BlinkList
  • blogmarks
  • co.mments
  • connotea
  • del.icio.us
  • De.lirio.us
  • digg
  • Fark
  • feedmelinks
  • Furl
  • LinkaGoGo
  • Ma.gnolia
  • NewsVine
  • Netvouz
  • RawSugar
  • Reddit
  • scuttle
  • Shadows
  • Simpy
  • Smarking
  • Spurl
  • TailRank
  • Wists
  • YahooMyWeb

4 Responses to “Revisiting J2ME Object Serialization”

  1. Hi Eric,

    did you try to use a hashtable to cache the mapping from class names to Class objects?

    BTW:

    1. Why not use a static final int constants for all the classes (including those implementing persistent, not only primitive types), and then have a single large switch block? This will make the serialized data more compact and avoid string creation overhead while reading from the file. Make sure the code compiles to a jump table by chosing constants carefully.

    2. Use resurect(DataInputStream dis) to avoid (possibly nested) byte array and stream creation overhead.

    3. If you implement “public static Object resurrect(DataInputStream dis)”, you can re-use this method it in the resurrect method of classes implementing Persistent, supporting polimorphism when reading members.

    Best regards,
    Stefan

  2. Hi Stefan, thanks for your comments. Yes, a hash table is another possible way to handle the problem if it’s the class lookup that’s slow. If the instantiation view newInstance is slow that’s another problem. The reader didn’t test both cases so I don’t know what the specific problem was, though I suspect it was the class lookup.

    As for your other comments:

    1. You have to remember that with the J2ME Tech Tips I write code that is both compact and generic as much as possible. So that’s why I stored classnames in the stream. Yes, if you had a static list of classes to work from then what you suggest is a good thing to do.

    2. Agreed, and that’s in fact what I’ve done in my own programs.

    3. Again yes, though properly serializing subclasses is tricky and requires a good deal of thought.

    Eric

  3. Well, if you do not have a static list of classes, the ‘cname.equals( “com.mypackage.Employee” )’ approach does not work out, so I took that for granted :)

  4. When I emailed the reader with my suggestion, I didn’t actually provide any code, just a description of what to do…. so for this post I added a bit of code and I tried to make it simple. But there you go proving my point about how you need to keep optimizing! :-)

Leave a Reply