Principle 11.6.3. EFFECTIVE DESIGN: I/O Design.
If an object is going to be input and output to and from files, it should define its own I/O methods. An object contains all the relevant information needed to perform I/O correctly.
java.io
package also provides methods for reading and writing objects, a process known as object serialization. Objects can be converted into a sequence of bytes, or serialized, by using the ObjectOutputStream
class, and they can be deserialized, or converted from bytes into a structured object, by using the ObjectInputStream
class (Figure 11.6.1). Despite the complexity of the serialization/deserialization processes, the methods in these classes make the task just as easy as reading and writing primitive data.Serializable
Interface
Student
class (Listing 11.6.2). In order to serialize an object, it must be a member of a class that implements the Serializable
interface. The Serializable
interface is a marker interface, an interface that doesn’t define any methods or constants but just serves to designate whether an object can be serialized or not.import java.io.*;
public class Student implements Serializable {
private String name;
private int year;
private double gpa;
public Student() {}
public Student (String nameIn, int yr, double gpaIn) {
name = nameIn;
year = yr;
gpa = gpaIn;
}
public void writeToFile(FileOutputStream outStream)
throws IOException{
ObjectOutputStream ooStream = new ObjectOutputStream(outStream);
ooStream.writeObject(this);
ooStream.flush();
} // writeToFile()
public void readFromFile(FileInputStream inStream)
throws IOException, ClassNotFoundException {
ObjectInputStream oiStream = new ObjectInputStream(inStream);
Student s = (Student)oiStream.readObject();
this.name = s.name;
this.year = s.year;
this.gpa = s.gpa;
} // readFromFile()
public String toString() {
return name + "\t" + year + "\t" + gpa;
}
} // Student
Student
class.Student
class contains its own I/O methods, readFromFile()
and writeToFile()
. This is an appropriate object-oriented design. The Student
class encapsulates all the relevant information needed to read and write its data.writeToFile()
method, which performs the output task. This method’s FileOutputStream
parameter is used to create an ObjectOutputStream
, whose writeObject()
method writes the object into the file. To output a Student
object, we merely invoke the writeObject()
method. This method writes out the current values of all the object’s public and private fields. In this case, the method would write a String
for the object’s name
, an int
for the object’s year
, and a double
for the object’s gpa
.writeObject()
method can also handle fields that refer to other objects. For example, suppose our Student
object provided a field for courses
that contained a reference to an array of objects, each of which described a course the student has taken. In that case, the writeObject()
method would serialize the array and all its objects (assuming they are serializable). Thus, when a complex object is serialized, the result would be a complex structure that contains all the data linked to that root object.readFromFile()
method, is simply the reverse of the serialization process. The readObject()
method reads one serialized object from the ObjectInputStream
. Its result type is Object
, so it is necessary to cast the result into the proper type. In our example we use a local Student
variable to store the object as it is input. We then copy each field of the local object to this
object.readFromFile()
method throws both the IOException
and ClassNotFoundException
. An IOException
will be generated if the file you are attempting to read does not contain serialized objects of the correct type. Objects that can be input by readObject()
are those that were output by writeObject()
. Thus, just as in the case of binary I/O, it is best to design an object’s input and output routines together so that they are compatible. The ClassNotFoundException
will be thrown if the Student
class cannot be found. This is needed to determine how to deserialize the object.ObjectOutputStream
and ObjectInputStream
, should be used whenever an object needs to be input or output from a stream.ObjectIO
Class
Student
class, let’s now write a user interface that can read and write Student
objects. We can use the same interface we used in the BinaryIO
program. The only things we need to change are the writeRecords()
and readRecords()
methods. Everything else about this program will be exactly the same as in BinaryIO
.ObjectIO
class. Note that the writeRecords()
method will still write five random records to the data file. The difference in this case is that we will call the Student.writeToFile()
method to take care of the actual output operations. The revised algorithm will create a new Student
object, using randomly generated data for its name, year, and GPA and then invoke its writeToFile()
to output its data. Note how a FileOutputStream
is created and passed to the Student.\-writeToFile()
method.import javax.swing.*; // Swing components
import java.awt.*;
import java.io.*;
import java.awt.event.*;
public class ObjectIO extends JFrame implements ActionListener{
private JTextArea display = new JTextArea();
private JButton read = new JButton("Read From File"),
write = new JButton("Write to File");
private JTextField nameField = new JTextField(10);
private JLabel prompt = new JLabel("Filename:",JLabel.RIGHT);
private JPanel commands = new JPanel();
public ObjectIO () {
super("ObjectIO Demo"); // Set window title
read.addActionListener(this);
write.addActionListener(this);
commands.setLayout(new GridLayout(2,2,1,1));
commands.add(prompt); // Control panel
commands.add(nameField);
commands.add(read);
commands.add(write);
display.setLineWrap(true);
this.getContentPane().setLayout(new BorderLayout () );
this.getContentPane().add("North",commands);
this.getContentPane().add( new JScrollPane(display));
this.getContentPane().add("Center", display);
} // ObjectIO
public void actionPerformed(ActionEvent evt) {
String fileName = nameField.getText();
if (evt.getSource() == read)
readRecords(fileName);
else
writeRecords(fileName);
} // actionPerformed()
private void readRecords(String fileName) {
try {
FileInputStream inStream = new FileInputStream(fileName); // Open a stream
display.setText("Name\tYear\tGPA\n");
try {
while (true) { // Infinite loop
Student student = new Student(); // Create a student instance
student.readFromFile(inStream); // and have it read an object
display.append(student.toString() + "\n"); // and display it
}
} catch (IOException e) { // Until IOException
}
inStream.close(); // Close the stream
} catch (FileNotFoundException e) {
display.append("IOERROR: File NOT Found: " + fileName + "\n");
} catch (IOException e) {
display.append("IOERROR: " + e.getMessage() + "\n");
} catch (ClassNotFoundException e) {
display.append("ERROR: Class NOT found " + e.getMessage() + "\n");
}
} // readRecords()
private void writeRecords(String fileName) {
try {
FileOutputStream outStream = new FileOutputStream( fileName );// Open stream
for (int k = 0; k < 5 ; k++) { // Generate 5 random objects
String name = "name" + k; // Name
int year = (int)(2000 + Math.random() * 4); // Class year
double gpa = Math.random() * 12; // GPA
Student student = new Student(name, year, gpa); // Create the object
display.append("Output: "+ student.toString() +"\n"); // and display it
student.writeToFile(outStream) ; // and tell it to write data
} //for
outStream.close();
} catch (IOException e) {
display.append("IOERROR: " + e.getMessage() + "\n");
}
} // writeRecords()
public static void main(String args[]) {
ObjectIO io = new ObjectIO();
io.setSize( 400,200);
io.setVisible(true);
io.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0); // Quit the application
}
});
} // main()
} // ObjectIO
ObjectIO
class provides an interface to reading and writing files of Student
s.readRecords()
method (Fig. Listing 11.6.5, Part II) will read data from a file containing serialized Student
objects. To do so, it first creates a Student object and then invokes its readFromFile()
method, passing it a FileInputStream
. Note how the FileInputStream
is created and, unlike in BinaryIO
, the inner try block is exited by an IOException rather than an EOFException
.SomeObject
s be readable by either the BinaryIO
or the ObjectIO
programs? Explain.