I/O streams are the foundation of most of the Java I/O and include a plethora of ready-made streams for any occasion, but they are very confusing to use if some context is not provided. A stream (like a river) represents an inflow/outflow of data. Think about it this way. When you type, you create a stream of characters that the system receives (input stream). When the system produces sounds, it sends them to the speaker (output stream).
The system could be receiving keystrokes and sending sound all day long, and thus the streams can be either processing data or waiting for more data.
When a stream doesn’t receive any data, it waits (nothing else to do, right?). As soon as data comes in, the stream starts processing this data. The stream then stops and waits for the next data item to come. This keeps going until this proverbial river becomes dry (the stream is closed).
Like a river, streams can be connected to each other (this is the decorator pattern). For the content of this chapter, there are mainly two input streams that you care about. One of them is the file input stream, and the other is the network socket input stream. These two streams are a source of data for your I/O programs. There are also their corresponding output streams: file output stream and the network socket output streams (how creative isn’t it?).
Like a plumber, you can hook them together and create something new. For example, you could weld together a file input stream to a network output stream to send the contents of the file through a network socket. Or you
CHAPTER 8 N INPUT AND OUTPUT
There are other input and output streams that can be glued together. For example, there is a
BufferedInputStream, which allows you to read the data in chunks (it’s more efficient than reading it byte by byte), and DataOutputStream allows you to write Java primitives to an output stream (instead of just writing bytes). One of the most useful streams is the ObjectInputStream and ObjectOutputStream pair, which will allow you to serialize/deserialize object (there is a recipe for that here).
The decorator pattern allows you to keep plucking streams together to get many different effects. The beauty of this design is that you can actually create a stream that will take any input and produce any output, and then can be thrown together with every other stream.
8-1. Serializing Java Objects
Problem
You need to serialize a class (save the contents of the class) so that you can restore it at a later time.
Solution
Java implements a built-in serialization mechanism. You access that mechanism via the ObjectOutputStream class.
In the following example, the method saveSettings() uses an ObjectOutputStream to serialize the settings object in preparation for writing the object to disk:
public class Ch_8_1_SerializeExample { public static void main(String[] args) {
Ch_8_1_SerializeExample example = new Ch_8_1_SerializeExample();
example.start();
}
private void start() {
ProgramSettings settings = new ProgramSettings( new Point(10,10), new Dimension(300,200), Color.blue,
"The title of the application" );
saveSettings(settings,"settings.bin");
ProgramSettings loadedSettings = loadSettings("settings.bin");
System.out.println("Are settings are equal? :"+loadedSettings.equals(settings));
}
private void saveSettings(ProgramSettings settings, String filename) { try {
FileOutputStream fos = new FileOutputStream(filename);
try (ObjectOutputStream oos = new ObjectOutputStream(fos)) { oos.writeObject(settings);
}
} catch (IOException e) { e.printStackTrace();
private ProgramSettings loadSettings(String filename) { try {
FileInputStream fis = new FileInputStream(filename);
ObjectInputStream ois = new ObjectInputStream(fis);
return (ProgramSettings) ois.readObject();
} catch (IOException | ClassNotFoundException e) { e.printStackTrace();
}
return null;
} }
How It Works
Java supports serialization, which is the capability of taking an object and creating a byte representation that can be used to restore the object at a later time. By using an internal serialization mechanism, most of the setup to serialize objects is taken care of. Java will transform the properties of an object into a byte stream, which can then be saved to a file or transmitted over the wire.
Note
N The original Java Serialization framework uses reflection to serialize the objects, so it might be an issue if serializing/deserializing heavily. There are plenty of open source frameworks that offer different trade-offs depending on your need (speed versus size versus ease of use). See https://github.com/eishay/jvm-serializers/wiki/.
For a class to be serializable, it needs to implement the Serializable interface, which is a Marker interface: it doesn’t have any methods, but instead tells the serialization mechanism that you have allowed the ability of your class to be serialized. While not evident from the onset, serialization exposes all the internal workings of your class (including protected and private members), so if you want to keep secret the authorization code for a nuclear launch, you might want to make any class that contains such information nonserializable.
It is also necessary that all properties (a.k.a. members, variables, or fields) of the class are serializable (and/or transient, which we will get to in a minute). All primitives—int, long, double, and float (plus their wrapper classes)—and the String class, are serializable by design. Other Java classes are serializable on a case-by-case basis.
For example, you can’t serialize any Swing components (like JButton or JSpinner), and you can’t serialize File objects, but you can serialize the Color class (awt.color, to be more precise).
As a design principle you don’t want to serialize your main classes, but instead you want to create classes that contain only the properties that you want to serialize. It will save a lot of headache in debugging because serialization becomes very pervasive. If you mark a major class as serializable (implements Serializable), and this class contains many other properties, you need to declare those classes as serializable as well. If your Java class inherits from another class, the parent class must also be serializable. In that way, you will find marking classes serializable when they really shouldn’t be.
If you want to mark a property as nonserializable, you may mark it as transient. Transient properties tell the Java compiler that you are not interested in saving/loading the property value, so it will be ignored. Some properties are good candidates for being transient, like cached calculations, or a date formatter that you always instantiate to the same value.
By the virtue of the Serialization framework, static properties are not serializable; neither are static classes. The reason is that a static class cannot be instantiated. Therefore, if you save and then load the static class at the same
CHAPTER 8 N INPUT AND OUTPUT
The Java serialization mechanism works behind the scenes to convert and traverse every object within the class that is marked as Serializable. If an application contains objects within objects, and even perhaps contains cross- referenced objects, the Serialization framework will resolve those objects, and store only one copy of any object. Each property then gets translated to a byte[] representation. The format of the byte array includes the actual class name (for example: com.somewhere.over.the.rainbow.preferences.UserPreferences), followed by the encoding of the properties (which in turn may encode another object class, with its properties, etc., etc., ad infinitum).
For the curious, if you look at the file generated (even in a text editor), you can see the class name as almost the first part of the file.
Note Serialization is very brittle. By default, the Serialization framework generates a Stream Unique Identifier (SUID) that captures information about what fields are presented in the class, what kind they are (public/protected), and what is transient, among other things. Even a perceived slight modification of the class (for example, changing an int to a long property) will generate a new SUID. A class that has been saved with a prior SUID cannot be deserialized on the new SUID. This is done to protect the serialization/deserialization mechanism, while also protecting the designers.
You can actually tell the Java class to use a specific SUID. This will allow you to serialize classes, modify them, and then deserialize the original classes while implementing some backward compatibility. The danger you run into is that the deserialization must be backward-compatible. Renaming or removing fields will generate an exception as the class is being deserialized. If you are specifying your own serial Serializable on your Serializable class, be sure to have some unit tests for backward-compatibility every time you change the class. In general, the changes that can be made on a class to keep it backward-compatible are found here: http://docs.oracle.com/javase/7/docs/platform/serialization/
spec/serial-arch.html.
Due to the nature of serialization, don’t expect constructors to be called when an object is deserialized. If you have initialization code in constructors that is required for your object to function properly, you may need to refactor the code out of the constructor to allow proper execution after construction. The reason is that in the deserialization process, the deserialized objects are “restored” internally (not created) and don’t invoke constructors.
8-2. Serializing Java Objects More Efficiently
Problem
You want to serialize a class, but want to make the output more efficient, or smaller in size, than the product generated via the built-in serialization method.
Solution
By making the object implement the Externalizable interface, you instruct the Java Virtual Machine to use a custom serialization/deserialization mechanism, as provided by the readExternal/writeExternal methods in the following example.
public class ExternalizableProgramSettings implements Externalizable { private Point locationOnScreen;
private Dimension frameSize;
private Color defaultFontColor;
private String title;
// Empty constructor, required for Externalizable implementors public ExternalizableProgramSettings() {
}
@Override
public void writeExternal(ObjectOutput out) throws IOException { out.writeInt(locationOnScreen.x);
out.writeInt(locationOnScreen.y);
out.writeInt(frameSize.width);
out.writeInt(frameSize.height);
out.writeInt(defaultFontColor.getRGB());
out.writeUTF(title);
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { locationOnScreen = new Point(in.readInt(), in.readInt());
frameSize = new Dimension(in.readInt(), in.readInt());
defaultFontColor = new Color(in.readInt());
title = in.readUTF();
}
// getters and setters omitted for brevity }
How It Works
The Java Serialization framework provides the ability for you to specify the implementation for serializing an object. As such, it requires implementing the Externalizable interface in lieu of the Serializable interface. The Externalizable interface contains two methods: writeExternal(ObjectOutput out) and readExternal(ObjectInput in). By implementing these methods, you are telling the framework how to encode/
decode your object.
The writeExternal() method will pass in as a parameter an ObjectOutput object. This object will then let you write your own encoding for the serialization. The ObjectOutput contains the methods listed in Table 8-1.
CHAPTER 8 N INPUT AND OUTPUT
One reason you may choose to implement the Externalizable interface instead of the Serializable interface is because Java’s default serialization is very inefficient. Because the Java Serialization framework needs to ensure that every object (and dependent object) is serialized, it will write even objects that have default values or that might be empty and/or null. Implementing the Externalizable interface also provides for finer-grained control on how your class is being serialized. In our example, the Serializable version created a setting of 439 bytes, compared with the Externalizable version of only 103 bytes!
Note
N Classes that implement the Externalizable interface must contain an empty (no-arg) constructor.
8-3. Serializing Java Objects as XML
Problem
Although you love the Serialization framework, you want to create something that is at least cross-language-compatible (or human readable). You would like to save and load your objects using XML.
Solution
In this example, the XMLEncoder object is used to encode the Settings object, which contains program settings information and writes it to the settings.xml file. The XMLDecoder takes the settings.xml file and reads it as a stream, decoding the Settings object. A FileSystem is used to gain access to the machine’s file system;
FileOutputStream is used to write a file to the system; and FileInputStream is used to obtain input bytes from a file within the file system. In this example, these three file objects are used to create new XML files, as well as read them for processing.
Table 8-1. ObjectOutput Methods
ObjectOutput ObjectInput Description
writeBoolean (boolean v) booleanreadBoolean () Read/writes the Boolean primitive.
writeByte(int v) intreadByte() Read/writes a byte.
Note: Java doesn’t have a byte primitive, so an int is used as a parameter, but only the least-significant byte will be written.
writeShort(int v) intreadShort() Read/writes two bytes.
Note: Only the two least-significant bytes will be written.
writeChar(int v) intreadChar() Read/writes two bytes as a char (reverse order than writeShort).
writeInt (int v) intreadInt() Read/writes an integer.
writeLong (long v) intreadLong() Read/writes a long.
writeDouble (double v) double readDouble Read/writes a double.
//Encoding
FileSystem fileSystem = FileSystems.getDefault();
try (FileOutputStream fos = new FileOutputStream("settings.xml"); XMLEncoder encoder = new XMLEncoder(fos)) {
encoder.setExceptionListener((Exception e) -> { System.out.println("Exception! :"+e.toString());
});
encoder.writeObject(settings);
}
// Decoding
try (FileInputStream fis = new FileInputStream("settings.xml"); XMLDecoder decoder = new XMLDecoder(fis)) {
ProgramSettings decodedSettings = (ProgramSettings) decoder.readObject();
System.out.println("Is same? "+settings.equals(decodedSettings));
}
Path file= fileSystem.getPath("settings.xml");
List<String> xmlLines = Files.readAllLines(file, Charset.defaultCharset());
xmlLines.stream().forEach((line) -> { System.out.println(line);
});
How It Works
XMLEncoder and XMLDecoder, like the Serialization framework, use reflection to determine which fields are to be written, but instead of writing the fields as binary, they are written as XML. Objects that are to be encoded do not need to be serializable, but they do need to follow the Java Beans specification.
Java Bean is the name of any object that conforms to the following contract:
The object contains a public empty (
u no-arg) constructor.
The object contains public getters and setters for each protected/private property that takes u
the name of get{Property}() and set{Property}().
The XMLEncoder and XMLDecoder will encode/decode only the properties of the Bean that have public accessors (get{property}, set{property}), so any properties that are private and do not have accessors will not be encoded/decoded.
Tip
N It is a good idea to register an Exception Listener when encoding/decoding.
The XmlEncoder creates a new instance of the class that being serialized (remember that they need to be Java Beans, so they must have an empty no-arg constructor), and then figures out which properties are accessible (via get{property}, set{property}). And if a property of the newly instantiated class contains the same value as the property of the original class (i.e., has the same default value), the XmlEncoder doesn’t write that property. In other words, if the default value of a property hasn’t changed, the XmlEncoder will not write it out. This provides the flexibility of changing what a “default” value is between versions. For example, if the default value of a property is 2
CHAPTER 8 N INPUT AND OUTPUT
The XMLEncoder also keeps track of references. If an object appears more than once when being persisted in the object graph (for example, an object is inside a Map from the main class, but is also as the DefaultValue property), then the XMLEncoder will only encode it once, and link up a reference by putting a link in the xml. The XMLEncoder/XMLDecoder is much more forgiving than the Serialization framework. When decoding, if a property type is changed, or if it was deleted/added/moved/renamed, the decoding will decode “as much as it can” while skipping the properties that it couldn’t decode.
The recommendation is to not persist your main classes (even though the XMLEncoder is more forgiving), but to create special objects that are simple, hold the basic information, and do not perform many tasks by themselves.
8-4. Creating a Socket Connection and Sending Serializable Objects Across the Wire
Problem
You need to open a network connection, and send/receive objects from it.
Solution
Use Java’s New Input Output API version 2 (NIO.2) to send and receive objects. The following solution utilizes the NIO.2 features of nonblocking sockets (by using Future tasks):
// Server Side
hostAddress = new InetSocketAddress(InetAddress.getByName("127.0.0.1"), 2583);
Future<AsynchronousSocketChannel> serverFuture = null;
AsynchronousServerSocketChannel serverSocketChannel = AsynchronousServerSocketChannel.open().bind(hostAddress);
serverFuture = serverSocketChannel.accept();
final AsynchronousSocketChannel clientSocket = serverFuture.get(2000, TimeUnit.MILLISECONDS);
System.out.println("Connected!");
if ((clientSocket != null) && (clientSocket.isOpen())) {
InputStream connectionInputStream = Channels.newInputStream(clientSocket);
ObjectInputStream ois = null;
ois = new ObjectInputStream(connectionInputStream);
while (true) {
Object object = ois.readObject();
if (object.equals("EOF")) {
connectionCount.decrementAndGet();
clientSocket.close();
break;
}
System.out.println("Received :" + object);
}
ois.close();
connectionInputStream.close();
}