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: J2ME, Java ME, serialization, coding, programming, CLDC, learning Java
Share and Enjoy:These icons link to social bookmarking sites where readers can share and discover new web pages.