16 April 2001 Simon Gronlund is earning his Master of Science in Computer Science at the Royal Institute of Technology, Sweden, as an adult student. He also teaches Java and computer-related courses at the college. When he isn't tampering with his Warp 4 PC, he spends his spare time with his two boys and his wife. If you have a comment about the content of this article, please feel free to vent in the OS/2 eZine discussion forums |
|
Into Java, Part 16
Today we will explore a convenient way to store and retrieve your
application data, something that can be really tiresome in other programming languages, but is a breeze with Java.
In fact any data may be stored this way, basic data types or classes built by you or Java default classes. More
than that, this is a rather fast way to store and read data from disk as well as from other streams. And it is
by far the easiest technique to implement. |
Serializable
interface
that resides in the java.io
package.
It is an empty interface that only tells the Java compiler that we know that this object may get serialized. We
need to add implement Serializable
to our class header. That is it, you are done!Briefly though, when an object stream saves your object(s) it needs
to save a description of the class, further it saves a seal and some more details, I will not go into the details of
that. Important to know though, is that it is the object's state that is saved, that is, any instance variable is saved. This
is a good reason to use instance variables sparsely, many times only a few arenecessary. Think "do I need
to save this variable to get an object back on its feet again?" If the answer is "yes" it needs to be
an instance variable, but if the value may be computed from other instance variables it is not a good candidate.
Sometimes we do not want to save an instance variable in any case, perhaps
for security reasons or if it holds vital transient information. Perhaps we can ask for it or recompute
it, for example machine related information as window sizes, or a value of limited life time. In these cases we use
the keyword transient
,
as
/* a value that is useless on another machine with other screen resolutions */
private transient int windowWidth;
Since it is possible, though tiresome, to exploit
the information of the stream, it is good to use transient
for any variable you do not want to reveal, or possibly that needs to be encrypted.The observant reader will notice that the same reasoning must hold
for variables declared static
that
is a class variable common to every instance of the class, while an instance variable
holds the state of a distinct object. If we need to recall the value of a static variable we need to take care
of that on our own. Having covered this use of static and transient, I consider this topic closed for now.
Finally, reading an object back from an object stream never
calls any method nor the constructor, it simply reinstates the proper values in the reincarnated object. Hence,
nothing is computed at the rebirth. If this is needed, find some deeper readings on this topic to find out
how you can do that.
xxxInt, xxxDouble
, etc., (where xxx is replaced by read
or write
)
take care of the basic data types.While basic data types are read and written with specific methods,
reading and writing objects is done with the xxxObject
methods. As with getting objects back from data structures, we must apply a cast to
the returned value, which is of the basic Object
type,
to the type we know it should be.
If using this technique for reading and writing data one by one, it
is important that the read-back is done in exactly the same order the writing was done, using the matching methods.
When you have a lot of data, this is really tiresome. In a moment I will show you a more
convenient way.
There is much more to tell on this topic, but I think that will be beyond
the scope of this column. One good resource is "Core Java 2, Volume 1 ", by Horstmann & Cornell, starting at
page 664. I would like to mention that there are several ways to twist both how data and what data should be written
to the stream. Naturally in such cases we must use refined techniques to get the data back again. The door is
open, you decide your direction.
PaintBox
, add two lines in the Paintshape
class. First we must import java.io
and then tell Java that we want to implement the behavior of Serializable
objects. In this class
there are no transient variables or anything else to consider.
|
Now we are ready with the class
PaintShape
. Let us continue with
PaintPanel
and see how we use object streams.
As expected, both ObjectInputStream
and ObjectOutputStream
are located in java.io
which needs to be imported. We will first look into
how to save our PaintShape
objects.
We will simply make a stream, and then write the objects to that
stream. Add a small method saveFile
,
or any name you like, somewhere in the PaintPanel
class, as the figure beneath shows. We do not have to implement
Serializable
in this class since this class is not going
to be saved, we are only interested in saving the painted shapes, aren't we?
|
If you would like to test it so far, add a single line to the PaintBox
class:
This new line will save the painting you sketched when you are exiting the program. Of course you are still not happy with that, there is no way to get the painting back. But in any case, you may find the
- find the constructor we named
firstInit.
- as the very first line of the
windowClosing
method block,
addpaintArea.saveFile();
immediately before theSystem.exit(0)
line.
paint.dat
file saved and you can inspect it a little. Some parts of it are readable, but most of it is not.
Please look back into the code. First we create the object output
stream, this time wrapped around a FileOutputStream
. Recall that an object stream shall be wrapped around anything extending a stream, InputStream
or
OutputStream
.
Kindly note that we are writing the entire
Vector storedShapes
to the stream. We need not save
one shape after another or use another similar scheme, it is possible to save a complete data structure. And if it is,
for example, sorted in a certain way, upon reading it back later on, it will still be sorted in the very same way.
If there are dependencies between the stored objects, they will be valid after they are read back. Convenient,
huh?
Would you like to get the painting back? It is as simple as saving
it. Still in the PaintPanel
we
add a little method like this one.
|
As with the former method, we have to catch
IOException
if anything goes wrong with the file.
But another exception is introduced, ClassNotFoundException
, that is thrown when a class referred to in the object file is not present
as a class file. Hence we find it necessary to always bring the class files along with the object file, they need
each other. Later on, erase the PaintShape.class
file
and try to read the data file.
After creating the object input stream around a
FileInputStream
we read from it. Again please note how
conveniently it is done, we simply read an object from the stream and assign the object to an instance variable storedShapes
, that is the Vector
we used when saving the painting.
But since readObject
returns
an Object
we need to apply
a cast to a Vector
, as
we know that storedShapes
is
declared to be of type Vector
.
Assigning a new vector to the old variable is simply a replacement,
we replace the old data structure (a Vector
)
with a new one.
Would you like to go for a test ride? Then simply add the line
as the very last line of the methodpaintArea.readFile();
init
in the PaintBox
class, after that the paintArea
object is instantiated. That is, as soon as the canvas is ready we call
the readFile
method which
replaces the still empty storedShapes Vector
with
a new stuffed one at start-up time. Try that, and say ... Wow!If you followed the instructions exactly, you now can save your
paintings. Unfortunately only one, and you can not erase the data file since that will crash the program at start,
due to an IOException
.
We need a better way to call the save and read methods, don't we?
|
Setting up a menu bar is only done once, as there is only one menu
bar in an application. The first two lines create one and set it to the frame, that is
setJMenuBar(bar)
is called from within the class extending JFrame
. In our case that will be PaintBox
. I have chosen to put the
entire code within a method of its own, secondInit
,
which we can call from the PaintBox
constructor
as the second line, between firstInit
and init
.
The second part of the code is to create a menu, the "File"
menu, which we simply add to the menu bar. Having more menus is done the same way, adding each to the same menu
bar. Of course, menus may have fancy stuff like mnemonics and so forth, but I will omit that for now.
Then there is the first real menu item created, the "Open
'paint.dat'" item. So far there is nothing to worry about, but of course menus may have icons, mnemonics,
key accelerators and so forth, too. But for now we must recall how to add a listener to that item. That is
done by adding an ActionListener
instantiated
on the fly, supplying an actionPerformed
method.
Finally we add the menu item to the menu.
Each menu item contains an ActionListener
object, but it would also be fine to make the whole
class implement ActionListener
and
add the method actionPerformed
,
containing a few if - if else
tests
with the very same method calls. The choice is yours, and we have tested both ways in previous IntoJava installments.
As seen in the code above, we do call the two methods we created
in the PaintPanel
class.
We call System.exit(0)
once.
We added two separators, for sharpness, and in order to learn how. But we have not seen the method clearShapes
that seems to be resident
in PaintPanel
. Here it
comes, a convenient way to start over with a new painting:
|
It certainly is easy with Java, isn't it? Only a single call to
the prefabricated Vector
class
removes every shape we have made, and we are ready for another sketch. Unfortunately I still do not know how to
overcome that the last item is not always cleared, but vanishes as soon as you start to paint a new object. Anyone
have an idea? Call me!.
Do not forget to remove the lines you added before when
testing the first two methods. Compile and go!
JFileChooser
. At the
same time we will briefly look into the handy dialogs that come with Swing, the
JOptionPane
.As a convenience I have included all the necessary java files as they
looked before we started with this column:
PaintBox.java
PaintPanel.java
PaintShape.java
SlidePanel.java
TopButtonPanel.java
We have learned how to make easy use of object streams, and that they
can handle complete data structures in a pinch. Naturally we can use these streams a lot more, and we will. We
will also, in a future column, see how convenient they are when sending data over a network, LAN or WAN.
We have also learned how to add menus to our applications, it looked
tiresome, and it sure is most of the time. But not that hard, don't you think?
CU next month.
Previous Article |
|
Next Article |