Saving Objects in Java
Articles —> Saving Objects in Java
Saving object in java can be accomplished in a number of ways, the most prominent of which is to serialize the object - saving it to a file and reading the object using an ObjectOutputStream and ObjectInputStream, respectively. Serialization is a fantastic advantage to using java. However, it has its drawbacks. First, serialization has a restriction to versioning - changes to an object can result in difficulty with backwards compatibility in reading objects from a file. Second, saving objects result in unintelligible data, not readily readable in other ways, for example with other software, scripts, or programming languages. An alternative to serialization is to customize a file format, however in some cases this customization results in multiple incompatible file types storing the same information. In this article, I discuss the possibility of using the power of java reflection to write the values contained within an object into a text file.
Java Reflection
The Java Reflection package allows a programmer to modify the runtime behavior of an application, and can be used to translate objects into text. The advantages to this approach is that the resulting text can be manipulated in other ways. The disadvantage is the same: making the inner workings, classes, and methods of an application known beyond the application itself. Nevertheless, in an environment where a common file format is required, and the inner workings of an application not restrictive, translating objects into text has a great advantage.
Java reflection is an advanced topic, and I'd recommend users to become familiar with the API before reading further1. The reflection API allows a programmer to access public values of a class. Given the complexity of public, private, and protected members, we must maintain a consistency in order to save our objects. In this case, we will rely on the public methods. To retrieve private, protected, or public instance variables, we will rely in the getter methods. Alternatively, to set values, we will rely on the setter methods.
Imagine the simple class:
/** * A Simple class to demonstrate Reflection in accessing instance variables. * @author Greg Cope * */ public class Tester { private int value1 = 0; private int value2 = 10; /** * */ public Tester(){ } /** * Retrieves value1 * @return */ public int getValue1(){ return value1; } /** * Sets the value1 * @param val */ public void setValue1(int val){ value1 = val; } /** * Retrieves value2 * @return */ public double getValue2(){ return value2; } /** * Sets the value2. * @param val2 */ public void setValue2(int val2){ value2 = val2; } }
Above is a basic class with 2 instance variables, both with public methods to get and set the values. Easy enough.
Accessing Object Values
Now the fun part, we can access these methods using java reflection:
/** * Utility class to save an object * @author Greg Cope * */ public class Saver{ /** * Uses reflection to access the parameter object. * @param o The object to access */ public void saveObject(Object o) throws Exception{ Method[] methods = o.getClass().getMethods(); for ( Method m : methods ){ if ( m.getName().indexOf("get") == 0 ){ System.out.println(m.invoke(o)); } } } }
The above class is very simple. It contains a single method which, for demonstration purposes, prints out information about the object. It first retrieves all the public methods of the object, then filters those methods to find only those which begin with the word 'get'. Lastly, if the method begins with the word get, it invokes the method and prints out the resulting returned value.
Of course the above demonstration is far from complete. We typically only wish to print out primitives, so what if the returned value of a getter is not a primitive, but another object? To solve this issue, we can check if the returned object is a primitive, and if not, send the returned object recursively to saveObject method:
/** * Utility class to save an object * @author Greg Cope * */ public class Saver{ //contains a set of values that are the names of primitives. private static Set<String> PRIMITIVES = new HashSet<String>(); static{ PRIMITIVES.add("int"); PRIMITIVES.add("float"); PRIMITIVES.add("String"); PRIMITIVES.add("double"); PRIMITIVES.add("char"); PRIMITIVES.add("byte"); PRIMITIVES.add("long"); } /** * Uses reflection to access the parameter object. * @param o The object to access */ public void saveObject(Object o) throws Exception{ Method[] methods = o.getClass().getMethods(); for ( Method m : methods ){ if ( m.getName().indexOf("get") == 0 ){ if (Saver.PRIMITIVES.contains(m.getReturnedType().getName())){ System.out.println(m.invoke(o)); }else{ saveObject(m.invoke(o)); } } } } }
The above code uses a Set to store the names of primitives, then checks this set for the name of the method's returned type. If it is a primitive, we print it out, if not we send that object to the saveObject method and recursively down the line.
There is one problem in this whole scene: the Object method getClass(). Using all the get's will also allow getClass to pass, printing out unforeseen values - but it is trivial to make sure the method name is not getClass with another conditional in mix.
Requirements
To be able to access the values of an object, each value one wishes to translate must have a public getter method. The getter is the bridge between the object and an abstract way of accessing its values using reflection. Alternatively, if one wishes to repopulate an object with these values, public setters can be used in a similar fashion, and default values should be specified (especially in the case of String's, which are not technically primitives but are treated as so in the above code, and if not instantiated to a default empty string value could result in a NullPointerException).
Problems
Of course it can't be that easy right? In the simplest of cases it can, but when things get more complex (and they typically do) issues can enter the equation quickly.
Circular References: Two objects have getters, each returning references to the other. Plugging that design into the above code is a recipe for an infinite loop. Ideally, one would catalog which objects have been saved. Upon entry into the saveObject method, one can check to catalog, doing nothing if the object has been previously saved.
Collections and Maps: These utilities are invaluable, but require their own special handling to retrieve the values within. Two additional methods, each for these interfaces can take care of these issues.
Complex Libraries: Any complex library, such as a Swing interface, could potentially pull out more information than one wishes. Care should be taken when passing these through reflection.
Of course there most likely will be other unforeseen issues that could arise, and it should be stressed that the above code listings are extremely primitive in this sense.
Uses
The above demonstration is a simple and very primitive one, but can be adapted in a number of situations.
Saving data to files: To save this to a file, a Writer can be passed to the saveObject method instead of printing values to the command line.
Debugging: Printing values out to the command line can be used in certain situations to allow you to know the values of an object.
Inter-program communication: Saving the values as a string can allow one to send the object values over a network, or allow one to print out values that can be parsed by other scripts written in other languages.
Of course, in most situations one will want to read the data back into memory. Saving the data in a distinct type, for example XML, will allow it to be parsed quite readily. However, I've rambled on long enough...parsing may be a topic of another discussion.
1The Reflection APIThere are no comments on this article.