|
|
(9 intermediate revisions by 2 users not shown) |
Line 1: |
Line 1: |
| By [[Simon Gronlund]] | | {{IntoJava}} |
| | ''By [[Simon Grönlund]]'' |
|
| |
|
| {| width="400" cellpadding="4"
| | ==Into Java, Part 16== |
| | align="CENTER" valign="MIDDLE" |
| | 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. |
| [[Into Java - Part I|Part I]]
| |
| | align="CENTER" valign="MIDDLE" |
| |
| [[Into Java - Part II|Part II]]
| |
| | align="CENTER" valign="MIDDLE" |
| |
| [[Into Java - Part III|Part III]]
| |
| | align="CENTER" valign="MIDDLE" |
| |
| [[Into Java - Part IV|Part IV]]
| |
| | align="CENTER" valign="MIDDLE" |
| |
| [[Into Java - Part V|Part V]]
| |
| | align="CENTER" valign="MIDDLE" |
| |
| [[Into Java - Part VI|Part VI]]
| |
| | align="CENTER" valign="MIDDLE" |
| |
| [[Into Java - Part VII|Part VII]]
| |
| | align="CENTER" valign="MIDDLE" |
| |
| [[Into Java - Part VIII|Part VIII]]
| |
| | align="CENTER" valign="MIDDLE" |
| |
| [[Into Java - Part IX|Part IX]]
| |
| | align="CENTER" valign="MIDDLE" |
| |
| [[Into Java - Part X|Part X]]
| |
| |}
| |
|
| |
|
| {| width="400" cellpadding="4"
| | As we have said, streams may take several forms and today we will look into object streams, both input and output. To handle them we must learn about <tt>Serializable</tt>, but don't worry, that is only a declarative interface with no methods to take care of. |
| | align="CENTER" valign="MIDDLE" |
| |
| [[Into Java - Part XI|Part XI]]
| |
| | align="CENTER" valign="MIDDLE" |
| |
| [[Into Java - Part XII|Part XII]]
| |
| | align="CENTER" valign="MIDDLE" |
| |
| [[Into Java - Part XIII|Part XIII]]
| |
| | align="CENTER" valign="MIDDLE" |
| |
| [[Into Java - Part XIV|Part XIV]]
| |
| | align="CENTER" valign="MIDDLE" |
| |
| [[Into Java - Part XV|Part XV]]
| |
| | align="CENTER" valign="MIDDLE" |
| |
| [[Into Java - Part XVI|Part XVI]]
| |
| | align="CENTER" valign="MIDDLE" |
| |
| [[Into Java - Part XVII|Part XVII]]
| |
| | align="CENTER" valign="MIDDLE" |
| |
| [[Into Java - Part XVIII|Part XVIII]]
| |
| | align="CENTER" valign="MIDDLE" |
| |
| [[Into Java - Part XIX|Part XIX]]
| |
| | align="CENTER" valign="MIDDLE" |
| |
| [[Into Java - Part XX|Part XX]]
| |
| | align="CENTER" valign="MIDDLE" |
| |
| [[Into Java - Part XXI|Part XXI]]
| |
| | align="CENTER" valign="MIDDLE" |
| |
| [[Into Java - Part XXII|Part XXII]]
| |
| | align="CENTER" valign="MIDDLE" |
| |
| [[Into Java - Part XXIII|Part XXIII]]
| |
| |}
| |
|
| |
|
| ==<font face="Helv,Helvetica,Arial">'''Into Java, Part 16<br />'''</font>==
| | As our application example we will use our old PaintBox. Until today we have not been able to save our paintings, but now we will enable that using a menu bar item. Yes, we will add menu bars to our repertoire. Using the Swing <tt>JMenuBar</tt> that is also a piece of cake, as are most of the graphic tools in Swing. |
|
| |
|
| <font size="-1" face="Helv,Helvetica,Arial">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.<br /></font>
| | ===Serialization interface=== |
| | The object stream saves the data to a particular file format not easily read by human eyes. The file format is not important, we do not need to know about its inner anatomy. In fact, we do not need to know that object streams use a file format of their own. But we do have to know that we must use the <tt>Serializable</tt> interface that resides in the <tt>java.io</tt> 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 <tt>implement Serializable</tt> to our class header. That is it, you are done! |
|
| |
|
| <font size="-1" face="Helv,Helvetica,Arial">As we have said, streams may take several forms and today we will look into object streams, both input and output. To handle them we must learn about</font><font size="-1"><code> Serializable</code></font><font size="-1" face="Helv,Helvetica,Arial">, but don't worry, that is only a declarative interface with no methods to take care of.<br /></font>
| | 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 are necessary. 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. |
|
| |
|
| <font size="-1" face="Helv,Helvetica,Arial">As our application example we will use our old PaintBox. Until today we have not been able to save our paintings, but now we will enable that using a menu bar item. Yes, we will add menu bars to our repertoire. Using the Swing</font><font size="-1"><code> JMenuBar </code></font><font size="-1" face="Helv,Helvetica,Arial">that is also a piece of cake, as are most of the graphic tools in Swing.<br /></font>
| | 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 <tt>transient</tt>, 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 <tt>transient</tt> for any variable you do not want to reveal, or possibly that needs to be encrypted. |
|
| |
|
| ===<font face="Helv,Helvetica,Arial">'''Serialization interface<br />'''</font>===
| | The observant reader will notice that the same reasoning must hold for variables declared <tt>static</tt> 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. |
|
| |
|
| <font size="-1" face="Helv,Helvetica,Arial">The object stream saves the data to a particular file format not easily read by human eyes. The file format is not important, we do not need to know about its inner anatomy. In fact, we do not need to know that object streams use a file format of their own. But we do have to know that we must use the</font><font size="-1"><code> Serializable </code></font><font size="-1" face="Helv,Helvetica,Arial">interface that resides in the</font><font size="-1"><code> java.io </code></font><font size="-1" face="Helv,Helvetica,Arial">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</font><font size="-1"><code> implement Serializable </code></font><font size="-1" face="Helv,Helvetica,Arial"> to our class header. That is it, you are done!<br /></font>
| | 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. |
|
| |
|
| <font size="-1" face="Helv,Helvetica,Arial">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.<br /></font>
| | ===Reading and writing objects=== |
| | The name "object stream" implies that only objects may be written and/or read to such streams. That is not true, the methods <tt>xxxInt, xxxDouble</tt>, etc., (where xxx is replaced by <tt>read</tt> or <tt>write</tt>) take care of the basic data types. |
|
| |
|
| <font size="-1" face="Helv,Helvetica,Arial">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</font><font size="-1"><code> transient</code></font><font size="-1" face="Helv,Helvetica,Arial">, as<br /></font> | | While basic data types are read and written with specific methods, reading and writing objects is done with the <tt>xxxObject</tt> methods. As with getting objects back from data structures, we must apply a cast to the returned value, which is of the basic <tt>Object</tt> type, to the type we know it should be. |
|
| |
|
| <blockquote><font size="-1"><code>/* a value that is useless on another machine with other screen resolutions */<br /> private transient int windowWidth;<br /></code></font></blockquote>
| | 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. |
|
| |
|
| <font size="-1" face="Helv,Helvetica,Arial">Since it '''is''' possible, though tiresome, to exploit the information of the stream, it is good to use</font><font size="-1"><code> transient </code></font><font size="-1" face="Helv,Helvetica,Arial">for any variable you do not want to reveal, or possibly that needs to be encrypted.<br /></font>
| | 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. |
|
| |
|
| <font size="-1" face="Helv,Helvetica,Arial">The observant reader will notice that the same reasoning must hold for variables declared</font><font size="-1"><code> static </code></font><font size="-1" face="Helv,Helvetica,Arial">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.<br /></font>
| | ===ObjectOutputStream and ObjectInputStream=== |
| | In our example application, the <tt>PaintBox</tt>, add two lines in the <tt>Paintshape</tt> class. First we must import <tt>java.io</tt> and then tell Java that we want to implement the behaviour of <tt>Serializable</tt> objects. In this class there are no transient variables or anything else to consider. |
|
| |
|
| <font size="-1" face="Helv,Helvetica,Arial">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.<br /></font>
| | [[Image:java16a.gif|493px|Serializable interface]] |
|
| |
|
| ====<font face="Helv,Helvetica,Arial">'''Reading and writing objects<br />'''</font>====
| | Now we are ready with the class <tt>PaintShape</tt>. Let us continue with <tt>PaintPanel</tt> and see how we use object streams. |
|
| |
|
| <font size="-1" face="Helv,Helvetica,Arial">The name "object stream" implies that only objects may be written and/or read to such streams. That is not true, the methods</font><font size="-1"><code> xxxInt, xxxDouble</code></font><font size="-1" face="Helv,Helvetica,Arial">, etc., (where xxx is replaced by</font><font size="-1"><code> read </code></font><font size="-1" face="Helv,Helvetica,Arial">or</font><font size="-1"><code> write</code></font><font size="-1" face="Helv,Helvetica,Arial">) take care of the basic data types.<br /></font> | | As expected, both <tt>ObjectInputStream</tt> and <tt>ObjectOutputStream</tt> are located in <tt>java.io</tt> which needs to be imported. We will first look into how to save our <tt>PaintShape</tt> objects. |
|
| |
|
| <font size="-1" face="Helv,Helvetica,Arial">While basic data types are read and written with specific methods, reading and writing objects is done with the</font><font size="-1"><code> xxxObject </code></font><font size="-1" face="Helv,Helvetica,Arial">methods. As with getting objects back from data structures, we must apply a cast to the returned value, which is of the basic</font><font size="-1"><code> Object </code></font><font size="-1" face="Helv,Helvetica,Arial">type, to the type we know it should be.<br /></font>
| | We will simply make a stream, and then write the objects to that stream. Add a small method <tt>saveFile</tt>, or any name you like, somewhere in the <tt>PaintPanel</tt> class, as the figure beneath shows. We do not have to implement <tt>Serializable</tt> in this class since this class is not going to be saved, we are only interested in saving the painted shapes, aren't we? |
|
| |
|
| <font size="-1" face="Helv,Helvetica,Arial">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.<br /></font>
| | [[Image:java16b.gif|585px|saveFile method - object stream]] |
|
| |
|
| <font size="-1" face="Helv,Helvetica,Arial">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.<br /></font>
| | If you would like to test it so far, add a single line to the <tt>PaintBox</tt> class: |
| | # find the constructor we named <tt>firstInit</tt>. |
| | # as the very first line of the <tt>windowClosing</tt> method block, '''add''' <tt>paintArea.saveFile();</tt> immediately before the <tt>System.exit(0)</tt> line. |
|
| |
|
| ===<font face="Helv,Helvetica,Arial">'''ObjectOutputStream and ObjectInputStream'''</font><br />===
| | 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 <tt>paint.dat</tt> file saved and you can inspect it a little. Some parts of it are readable, but most of it is not. |
|
| |
|
| <font size="-1" face="Helv,Helvetica,Arial">In our example application, the</font><font size="-1"><code> PaintBox</code></font><font size="-1" face="Helv,Helvetica,Arial">, add two lines in the</font><font size="-1"><code> Paintshape </code></font><font size="-1" face="Helv,Helvetica,Arial">class. First we must import</font><font size="-1"><code> java.io </code></font><font size="-1" face="Helv,Helvetica,Arial">and then tell Java that we want to implement the behavior of</font><font size="-1"><code> Serializable </code></font><font size="-1" face="Helv,Helvetica,Arial">objects. In this class there are no transient variables or anything else to consider.<br /><br /></font>
| | Please look back into the code. First we create the object output stream, this time wrapped around a <tt>FileOutputStream</tt>. Recall that an object stream shall be wrapped around anything extending a stream, <tt>InputStream</tt> or <tt>OutputStream</tt>. |
|
| |
|
| {| width="100%" cellspacing="5" cellpadding="2" align="LEFT" bgcolor="#00B6FF"
| | Kindly note that we are writing the entire <tt>Vector storedShapes</tt> 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? |
| |
| |
| {| width="100%" cellpadding="5" bgcolor="White"
| |
| |
| |
| [[Image:java16a.gif|493px|Serializable interface]]<br />
| |
| |}
| |
| |}
| |
|
| |
|
| <font size="-1" face="Helv,Helvetica,Arial">�<br clear="ALL" /></font> | | Would you like to get the painting back? It is as simple as saving it. Still in the <tt>PaintPanel</tt> we add a little method like this one. |
|
| |
|
| <font size="-1" face="Helv,Helvetica,Arial">Now we are ready with the class </font><font size="-1"><code> PaintShape</code></font><font size="-1" face="Helv,Helvetica,Arial">. Let us continue with</font><font size="-1"><code> PaintPanel </code></font><font size="-1" face="Helv,Helvetica,Arial">and see how we use object streams.<br /></font>
| | [[Image:java16c.gif|554px|readFile method - object stream]] |
|
| |
|
| <font size="-1" face="Helv,Helvetica,Arial">As expected, both</font><font size="-1"><code> ObjectInputStream </code></font><font size="-1" face="Helv,Helvetica,Arial">and</font><font size="-1"><code> ObjectOutputStream </code></font><font size="-1" face="Helv,Helvetica,Arial">are located in</font><font size="-1"><code> java.io </code></font><font size="-1" face="Helv,Helvetica,Arial">which needs to be imported. We will first look into how to save our</font><font size="-1"><code> PaintShape </code></font><font size="-1" face="Helv,Helvetica,Arial">objects.<br /></font>
| | As with the former method, we have to catch <tt>IOException</tt> if anything goes wrong with the file. But another exception is introduced, <tt>ClassNotFoundException</tt>, 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 <tt>PaintShape.class </tt> file and try to read the data file. |
|
| |
|
| <font size="-1" face="Helv,Helvetica,Arial">We will simply make a stream, and then write the objects to that stream. Add a small method</font><font size="-1"><code> saveFile</code></font><font size="-1" face="Helv,Helvetica,Arial">, or any name you like, somewhere in the</font><font size="-1"><code> PaintPanel </code></font><font size="-1" face="Helv,Helvetica,Arial">class, as the figure beneath shows. We do not have to implement</font><font size="-1"><code> Serializable </code></font><font size="-1" face="Helv,Helvetica,Arial">in this class since this class is not going to be saved, we are only interested in saving the painted shapes, aren't we?<br /></font> | | After creating the object input stream around a <tt>FileInputStream</tt> 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 <tt>storedShapes</tt>, that is the <tt>Vector</tt> we used when saving the painting. But since <tt>readObject</tt> returns an <tt>Object</tt> we need to apply a cast to a <tt>Vector</tt>, as we know that <tt>storedShapes</tt> is declared to be of type <tt>Vector</tt>. |
|
| |
|
| {| width="100%" cellspacing="5" cellpadding="2" align="LEFT" bgcolor="#00B6FF"
| | Assigning a new vector to the old variable is simply a replacement, we replace the old data structure (a <tt>Vector</tt>) with a new one. |
| |
| |
| {| width="100%" cellpadding="5" bgcolor="White"
| |
| |
| |
| [[Image:java16b.gif|585px|saveFile method - object stream]]<br />
| |
| |}
| |
| |}
| |
|
| |
|
| <br clear="ALL" /> | | Would you like to go for a test ride? Then simply add the line |
| | paintArea.readFile(); |
| | as the very last line of the method <tt>init</tt> in the <tt>PaintBox</tt> class, after that the <tt>paintArea</tt> object is instantiated. That is, as soon as the canvas is ready we call the <tt>readFile</tt> method which replaces the still empty <tt>storedShapes Vector</tt> with a new stuffed one at start-up time. Try that, and say... Wow! |
|
| |
|
| <font size="-1" face="Helv,Helvetica,Arial">If you would like to test it so far, add a single line to the</font><font size="-1"><code> PaintBox </code></font><font size="-1" face="Helv,Helvetica,Arial">class:<br /></font>
| | 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 <tt>IOException</tt>. We need a better way to call the save and read methods, don't we? |
|
| |
|
| <blockquote>
| | ===Menu bar=== |
| | Adding a menu bar is not hard, but adding a lot of menus and a lot of items to each menu can be tiresome (as the example shows). Today we will only add one menu with four menu items added to it. Here comes one example of what that might look like: |
|
| |
|
| # <font size="-1" face="Helv,Helvetica,Arial">find the constructor we named</font><font size="-1"><code> firstInit.</code></font><br />
| | [[Image:java16d.gif|542px|Adding menu and items]] |
| # <font size="-1" face="Helv,Helvetica,Arial">as the very first line of the</font><font size="-1"><code> windowClosing </code></font><font size="-1" face="Helv,Helvetica,Arial">method block,<br />'''add'''</font><font size="-1"><code> paintArea.saveFile();</code></font><br /><font size="-1" face="Helv,Helvetica,Arial">immediately before the</font><font size="-1"><code> System.exit(0) </code></font><font size="-1" face="Helv,Helvetica,Arial">line.<br /></font>
| |
|
| |
|
| <font size="-1" face="Helv,Helvetica,Arial">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</font><font size="-1"><code> paint.dat </code></font><font size="-1" face="Helv,Helvetica,Arial">file saved and you can inspect it a little. Some parts of it are readable, but most of it is not.<br /></font></blockquote>
| | 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 <tt>setJMenuBar(bar)</tt> is called from within the class extending <tt>JFrame</tt>. In our case that will be <tt>PaintBox</tt>. I have chosen to put the entire code within a method of its own, <tt>secondInit</tt>, which we can call from the <tt>PaintBox</tt> constructor as the second line, between <tt>firstInit</tt> and <tt>init</tt>. |
|
| |
|
| <font size="-1" face="Helv,Helvetica,Arial">Please look back into the code. First we create the object output stream, this time wrapped around a</font><font size="-1"><code> FileOutputStream</code></font><font size="-1" face="Helv,Helvetica,Arial">. Recall that an object stream shall be wrapped around anything extending a stream,</font><font size="-1"><code> InputStream </code></font><font size="-1" face="Helv,Helvetica,Arial">or</font><font size="-1"><code> OutputStream</code></font><font size="-1" face="Helv,Helvetica,Arial">.<br /></font>
| | 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. |
|
| |
|
| <font size="-1" face="Helv,Helvetica,Arial">Kindly note that we are writing the entire</font><font size="-1"><code> Vector storedShapes </code></font><font size="-1" face="Helv,Helvetica,Arial">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?<br /></font>
| | 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 <tt>ActionListener</tt> instantiated on the fly, supplying an <tt>actionPerformed</tt> method. Finally we add the menu item to the menu. |
|
| |
|
| <font size="-1" face="Helv,Helvetica,Arial">Would you like to get the painting back? It is as simple as saving it. Still in the</font><font size="-1"><code> PaintPanel </code></font><font size="-1" face="Helv,Helvetica,Arial">we add a little method like this one.<br /></font> | | Each menu item contains an <tt>ActionListener</tt> object, but it would also be fine to make the whole class <tt>implement ActionListener</tt> and add the method <tt>actionPerformed</tt>, containing a few <tt>if - if else</tt> tests with the very same method calls. The choice is yours, and we have tested both ways in previous IntoJava instalments. |
|
| |
|
| {| width="100%" cellspacing="5" cellpadding="2" align="LEFT" bgcolor="#00B6FF"
| | As seen in the code above, we do call the two methods we created in the <tt>PaintPanel</tt> class. We call <tt>System.exit(0)</tt> once. We added two separators, for sharpness, and in order to learn how. But we have not seen the method <tt>clearShapes</tt> that seems to be resident in <tt>PaintPanel</tt>. Here it comes, a convenient way to start over with a new painting: |
| |
| |
| {| width="100%" cellpadding="5" bgcolor="White"
| |
| |
| |
| [[Image:java16c.gif|554px|readFile method - object stream]]<br />
| |
| |}
| |
| |}
| |
|
| |
|
| <font size="-1" face="Helv,Helvetica,Arial">�<br clear="ALL" /></font>
| | [[Image:java16e.gif|371px|Method clearShapes in PaintPanel]] |
|
| |
|
| <font size="-1" face="Helv,Helvetica,Arial">As with the former method, we have to catch</font><font size="-1"><code> IOException </code></font><font size="-1" face="Helv,Helvetica,Arial">if anything goes wrong with the file. But another exception is introduced,</font><font size="-1"><code> ClassNotFoundException</code></font><font size="-1" face="Helv,Helvetica,Arial">, 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</font><font size="-1"><code> PaintShape.class </code></font><font size="-1" face="Helv,Helvetica,Arial">file and try to read the data file.<br /></font>
| | It certainly is easy with Java, isn't it? Only a single call to the prefabricated <tt>Vector</tt> 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? [mailto:d97-sgr@d.kth.se Call me]! |
|
| |
|
| <font size="-1" face="Helv,Helvetica,Arial">After creating the object input stream around a</font><font size="-1"><code> FileInputStream </code></font><font size="-1" face="Helv,Helvetica,Arial">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</font><font size="-1"><code> storedShapes</code></font><font size="-1" face="Helv,Helvetica,Arial">, that is the</font><font size="-1"><code> Vector </code></font><font size="-1" face="Helv,Helvetica,Arial">we used when saving the painting. But since</font><font size="-1"><code> readObject </code></font><font size="-1" face="Helv,Helvetica,Arial">returns an</font><font size="-1"><code> Object </code></font><font size="-1" face="Helv,Helvetica,Arial">we need to apply a cast to a</font><font size="-1"><code> Vector</code></font><font size="-1" face="Helv,Helvetica,Arial">, as we know that</font><font size="-1"><code> storedShapes </code></font><font size="-1" face="Helv,Helvetica,Arial">is declared to be of type</font><font size="-1"><code> Vector</code></font><font size="-1" face="Helv,Helvetica,Arial">.</font>
| | Do not forget to remove the lines you added before when testing the first two methods. Compile and go! |
|
| |
|
| <font size="-1" face="Helv,Helvetica,Arial">Assigning a new vector to the old variable is simply a replacement, we replace the old data structure (a</font><font size="-1"><code> Vector</code></font><font size="-1" face="Helv,Helvetica,Arial">) with a new one.<br /></font>
| | ===Summary=== |
| | Here you have a little application capable of saving your small, cute paintings. And restoring them. Unfortunately you have to use your favourite file manager to copy them into other file names if you (or your kids) like to do a lot of them. So next time we will look into how to use the <tt>JFileChooser</tt>. At the same time we will briefly look into the handy dialogs that come with Swing, the <tt>JOptionPane</tt>. |
|
| |
|
| <font size="-1" face="Helv,Helvetica,Arial"> </font>
| | 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. |
|
| |
|
| <blockquote> Would you like to go for a test ride? Then simply add the line<br /><blockquote><font size="-1"><code>paintArea.readFile();</code></font><br /></blockquote><font size="-1" face="Helv,Helvetica,Arial">as the very last line of the method</font><font size="-1"><code> init </code></font><font size="-1" face="Helv,Helvetica,Arial">in the</font><font size="-1"><code> PaintBox </code></font><font size="-1" face="Helv,Helvetica,Arial">class, after that the</font><font size="-1"><code> paintArea </code></font><font size="-1" face="Helv,Helvetica,Arial">object is instantiated. That is, as soon as the canvas is ready we call the</font><font size="-1"><code> readFile </code></font><font size="-1" face="Helv,Helvetica,Arial">method which replaces the still empty</font><font size="-1"><code> storedShapes Vector </code></font><font size="-1" face="Helv,Helvetica,Arial">with a new stuffed one at start-up time. Try that, and say ... Wow!<br /></font></blockquote>
| | 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? |
|
| |
|
| <font size="-1" face="Helv,Helvetica,Arial">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</font><font size="-1"><code> IOException</code></font><font size="-1" face="Helv,Helvetica,Arial">. We need a better way to call the save and read methods, don't we?<br /></font>
| | CU next month. |
| | | [[Category:Java Articles]] |
| ===<font face="Helv,Helvetica,Arial">'''Menu bar<br />'''</font>===
| |
| | |
| <font size="-1" face="Helv,Helvetica,Arial">Adding a menu bar is not hard, but adding a lot of menus and a lot of items to each menu can be tiresome (as the example shows). Today we will only add one menu with four menu items added to it. Here comes one example of what that might look like:<br /></font><br />
| |
| | |
| {| width="100%" cellspacing="5" cellpadding="2" align="LEFT" bgcolor="#00B6FF"
| |
| |
| |
| {| width="100%" cellpadding="5" bgcolor="White"
| |
| |
| |
| [[Image:java16d.gif|542px|Adding menu and items]]<br />
| |
| |}
| |
| |}
| |
| | |
| <font size="-1" face="Helv,Helvetica,Arial">�<br clear="ALL" /></font>
| |
| | |
| <font size="-1" face="Helv,Helvetica,Arial">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</font><font size="-1"><code> setJMenuBar(bar) </code></font><font size="-1" face="Helv,Helvetica,Arial">is called from within the class extending</font><font size="-1"><code> JFrame</code></font><font size="-1" face="Helv,Helvetica,Arial">. In our case that will be</font><font size="-1"><code> PaintBox</code></font><font size="-1" face="Helv,Helvetica,Arial">. I have chosen to put the entire code within a method of its own,</font><font size="-1"><code> secondInit</code></font><font size="-1" face="Helv,Helvetica,Arial">, which we can call from the</font><font size="-1"><code> PaintBox </code></font><font size="-1" face="Helv,Helvetica,Arial">constructor as the second line, between</font><font size="-1"><code> firstInit </code></font><font size="-1" face="Helv,Helvetica,Arial">and</font><font size="-1"><code> init</code></font><font size="-1" face="Helv,Helvetica,Arial">.<br /></font>
| |
| | |
| <font size="-1" face="Helv,Helvetica,Arial">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.<br /></font>
| |
| | |
| <font size="-1" face="Helv,Helvetica,Arial">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</font><font size="-1"><code> ActionListener </code></font><font size="-1" face="Helv,Helvetica,Arial">instantiated on the fly, supplying an</font><font size="-1"><code> actionPerformed </code></font><font size="-1" face="Helv,Helvetica,Arial">method. Finally we add the menu item to the menu.<br /></font>
| |
| | |
| <font size="-1" face="Helv,Helvetica,Arial">Each menu item contains an</font><font size="-1"><code> ActionListener </code></font><font size="-1" face="Helv,Helvetica,Arial">object, but it would also be fine to make the whole class</font><font size="-1"><code> implement ActionListener </code></font><font size="-1" face="Helv,Helvetica,Arial">and add the method</font><font size="-1"><code> actionPerformed</code></font><font size="-1" face="Helv,Helvetica,Arial">, containing a few</font><font size="-1"><code> if�-�if�else </code></font><font size="-1" face="Helv,Helvetica,Arial">tests with the very same method calls. The choice is yours, and we have tested both ways in previous IntoJava installments.<br /></font>
| |
| | |
| <font size="-1" face="Helv,Helvetica,Arial">As seen in the code above, we do call the two methods we created in the</font><font size="-1"><code> PaintPanel </code></font><font size="-1" face="Helv,Helvetica,Arial">class. We call</font><font size="-1"><code> System.exit(0) </code></font><font size="-1" face="Helv,Helvetica,Arial">once. We added two separators, for sharpness, and in order to learn how. But we have not seen the method</font><font size="-1"><code> clearShapes </code></font><font size="-1" face="Helv,Helvetica,Arial">that seems to be resident in</font><font size="-1"><code> PaintPanel</code></font><font size="-1" face="Helv,Helvetica,Arial">. Here it comes, a convenient way to start over with a new painting:<br /></font><br />
| |
| | |
| {| width="100%" cellspacing="5" cellpadding="2" align="LEFT" bgcolor="#00B6FF"
| |
| |
| |
| {| width="100%" cellpadding="5" bgcolor="White"
| |
| |
| |
| [[Image:java16e.gif|371px|Method clearShapes in PaintPanel]]<br />
| |
| |}
| |
| |}
| |
| | |
| <font size="-1" face="Helv,Helvetica,Arial">�<br clear="ALL" /></font>
| |
| | |
| <font size="-1" face="Helv,Helvetica,Arial">It certainly is easy with Java, isn't it? Only a single call to the prefabricated</font><font size="-1"><code> Vector </code></font><font size="-1" face="Helv,Helvetica,Arial">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? [mailto:d97-sgr@d.kth.se Call me]<nowiki>!.</nowiki><br /></font>
| |
| | |
| <font size="-1" face="Helv,Helvetica,Arial">Do not forget to remove the lines you added before when testing the first two methods. Compile and go!<br /></font>
| |
| | |
| {| cellspacing="3" cellpadding="1" align="RIGHT" bgcolor="#00B6FF"
| |
| |
| |
| <center>
| |
| | |
| {| width="100%" cellspacing="1" cellpadding="5" align="CENTER"
| |
| | colspan="2" bgcolor="White" align="CENTER" |
| |
| <center><font size="-1" face="Helv,Helvetica,Arial">Previous installments<br /></font></center>
| |
| |-
| |
| | colspan="2" bgcolor="White" valign="BOTTOM" | <font size="-1" face="Helv,Helvetica,Arial">[intojava.html Into Java, XV]</font><br />
| |
| |-
| |
| | bgcolor="White" valign="BOTTOM" | <font size="-1" face="Helv,Helvetica,Arial">[../20010116/java13.htm Into Java, XIII]</font><br />
| |
| | bgcolor="White" valign="BOTTOM" | <font size="-1" face="Helv,Helvetica,Arial">[../20010216/intojava.html Into Java, XIV]</font><br />
| |
| |-
| |
| | bgcolor="White" valign="BOTTOM" | <font size="-1" face="Helv,Helvetica,Arial">[../20001116/java11.htm Into Java, XI]</font><br />
| |
| | bgcolor="White" valign="BOTTOM" | <font size="-1" face="Helv,Helvetica,Arial">[../20001216/java12.htm Into Java, XII]</font><br />
| |
| |-
| |
| | bgcolor="White" valign="BOTTOM" | <font size="-1" face="Helv,Helvetica,Arial">[../20000916/java9.htm Into Java, IX]</font><br />
| |
| | bgcolor="White" valign="BOTTOM" | <font size="-1" face="Helv,Helvetica,Arial">[../20001016/java10.htm Into Java, X]</font><br />
| |
| |-
| |
| | bgcolor="White" align="CENTER" |
| |
| <center><font size="-1" face="Helv,Helvetica,Arial">[../20000716/java7.htm Into Java, VII]</font><br /></center>
| |
| | bgcolor="White" align="CENTER" |
| |
| <center><font size="-1" face="Helv,Helvetica,Arial">[../20000816/java8.htm Into Java, VIII]</font><br /></center>
| |
| |-
| |
| | bgcolor="White" align="CENTER" |
| |
| <center><font size="-1" face="Helv,Helvetica,Arial">[../20000516/intojava.html Into Java, V]</font><br /></center>
| |
| | bgcolor="White" align="CENTER" |
| |
| <center><font size="-1" face="Helv,Helvetica,Arial">[../20000616/java6.htm Into Java, VI]</font><br /></center>
| |
| |-
| |
| | bgcolor="White" align="CENTER" |
| |
| <center><font size="-1" face="Helv,Helvetica,Arial">[../v4n9/java3.htm Into Java, III]</font><br /></center>
| |
| | bgcolor="White" align="CENTER" |
| |
| <center><font size="-1" face="Helv,Helvetica,Arial">[../v5n1/intojava.html Into Java, IV]</font><br /></center>
| |
| |-
| |
| | bgcolor="White" align="CENTER" |
| |
| <center><font size="-1" face="Helv,Helvetica,Arial">[../v4n5/java.htm Into Java, I]</font><br /></center>
| |
| | bgcolor="White" align="CENTER" |
| |
| <center><font size="-1" face="Helv,Helvetica,Arial">[../v4n8/java2a.htm Into Java, II]</font><br /></center>
| |
| |}
| |
| | |
| </center>
| |
| |}
| |
| | |
| ===<font face="Helv,Helvetica,Arial">'''Summary<br />'''</font>===
| |
| | |
| <font size="-1" face="Helv,Helvetica,Arial">Here you have a little application capable of saving your small, cute paintings. And restoring them. Unfortunately you have to use your favorite file manager to copy them into other file names if you (or your kids) like to do a lot of them. So next time we will look into how to use the</font><font size="-1"><code> JFileChooser</code></font><font size="-1" face="Helv,Helvetica,Arial">. At the same time we will briefly look into the handy dialogs that come with Swing, the</font><font size="-1"><code> JOptionPane</code></font><font size="-1" face="Helv,Helvetica,Arial">.<br /></font>
| |
| | |
| <font size="-1" face="Helv,Helvetica,Arial">As a convenience I have included all the necessary java files as they looked before we started with this column:<br /></font>
| |
| | |
| <font size="-1" face="Helv,Helvetica,Arial"> </font>
| |
| | |
| <blockquote>
| |
| * [[Editing Into Java - Part XVI:PaintBox.java]]
| |
| * [[Editing Into Java - Part XVI:PaintPanel.java]]
| |
| * [[Editing Into Java - Part XVI:PaintShape.java]]
| |
| * [[Editing Into Java - Part XVI:SlidePanel.java]]
| |
| * [[Editing Into Java - Part XVI:TopButtonPanel.java]]
| |
| </blockquote>
| |
| | |
| <font size="-1" face="Helv,Helvetica,Arial">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.<br /></font>
| |
| | |
| <font size="-1" face="Helv,Helvetica,Arial">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?<br /></font>
| |
| | |
| <font size="-1" face="Helv,Helvetica,Arial">CU next month.<br /></font>
| |
| | |
| [[Categoy:Languages Articles]] | |
By Simon Grönlund
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.
As we have said, streams may take several forms and today we will look into object streams, both input and output. To handle them we must learn about Serializable, but don't worry, that is only a declarative interface with no methods to take care of.
As our application example we will use our old PaintBox. Until today we have not been able to save our paintings, but now we will enable that using a menu bar item. Yes, we will add menu bars to our repertoire. Using the Swing JMenuBar that is also a piece of cake, as are most of the graphic tools in Swing.
Serialization interface
The object stream saves the data to a particular file format not easily read by human eyes. The file format is not important, we do not need to know about its inner anatomy. In fact, we do not need to know that object streams use a file format of their own. But we do have to know that we must use the 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 are necessary. 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.
Reading and writing objects
The name "object stream" implies that only objects may be written and/or read to such streams. That is not true, the methods 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.
ObjectOutputStream and ObjectInputStream
In our example application, the PaintBox, add two lines in the Paintshape class. First we must import java.io and then tell Java that we want to implement the behaviour 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:
- find the constructor we named firstInit.
- as the very first line of the windowClosing method block, add paintArea.saveFile(); immediately before the System.exit(0) line.
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 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
paintArea.readFile();
as the very last line of the method 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?
Adding a menu bar is not hard, but adding a lot of menus and a lot of items to each menu can be tiresome (as the example shows). Today we will only add one menu with four menu items added to it. Here comes one example of what that might look like:
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 instalments.
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!
Summary
Here you have a little application capable of saving your small, cute paintings. And restoring them. Unfortunately you have to use your favourite file manager to copy them into other file names if you (or your kids) like to do a lot of them. So next time we will look into how to use the 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:
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.