The objects in our programs exist only while the program is executing. When the program closed, these objects cease to exist. How can we save an object in our program and restore it when the program rerun? For example, suppose that we are playing a computer chess game. We close the game before it finished. When we restart the game, it should resume from where we had left it, instead of from the beginning. One way to accomplish this would be to save information about the game (such as the locations of various game pieces, scores, and so forth) to a file, and then read this information back from the file to restore the game to the state where we had left it when it runs next. This is the idea behind serialization. Serialization is saving an object’s state as a binary stream (that is, as a sequence of bytes). When an object is serialized, it is said to be flattened or deflated. The reverse process of constructing an object from the binary data is known as deserialization. Thus, a deserialised (or inflated) object is one whose state has restored.
Java provides two classes to implement serialization: ObjectOutputStream and ObjectlnputStream. The ObjectOutputStream class is used to convert the instance fields of an object into a stream of bytes. This byte stream can then sent to a FileOutputStream, which writes it to a file. The binary data can then be read back from the file using a FilelnputStream and sent to an ObjectInputStream class to restore the object.
Below figure shows the readObject and writeObject methods of these two classes that are used to read and write objects, respectively. A class whose objects serialise must implement the java. io.Serializable interface. Otherwise, a NotSeria1izableException thrown at run time.
Although the state of objects can be saved to a file using the classes, it is much simpler to do so using these object stream classes. Additionally, these classes reduce the chances of introducing errors in your program-for example, by reading back the fields in a different order than they wrote in. If the object being saved contains references to other objects, the latter are also saved along with the former.
This next example shows how objects can serialise. Consider a class called AudioPlayer with a field called audioFile and a play method to play the contents of audioFile. Let us write a program in which we create an instance of this class called player that is playing some audio file. When we close the program and then rerun it later, we would like to have it replay the file that played last. It can be done by saving the value of the player’s audioFile field in some me, and then reading it back from this file when the program is rerun. The AudioPlayer class must implement the Serializable interface:
public class AudioPlayer implements Serializable {
public File audioFile;
public void setAudioFile(File f){ audioFile = f;}
public void play() throwsException{// code to play audioFile}
}
You will see how to write the code in method play to play an audio file a littie later. Create an instance of AudioPlayer called player:
AudioPlayer player = new AudioPlayer();
player.setAudioFile(new File(“audio/groovy.wav”))
Next, we discuss how to serialise this object player.
We’ll be covering the following topics in this tutorial:
Object Serialization
The process of reading and writing objects in a file is called object serialization. In Java, serialization occurs automatically.
An object is serialised using the following steps:
1. Create a FileOutputStream to write to a file (say, object.dat) and wrap it inside an ObjectOutputStream.
2. Invoke the object stream’s writeObject method to save the object’s state to object.dat.
For example, this shows how to serialise the instance player by saving its state in the file player.dat:
// wrap a FileOutputStream inside an ObjectOutputStream
FileOutputStream fileOut = new FileOutputStream(“text/player.dat”) ;
ObjectOutputStream objectOut = new ObjectOutputStream(fileOut);
// save the contents of field audioFile in player into file player.dat
objectOut.writeObject(player); objectOut.close();
In this example, the player contains only one field, but if there are multiple fields, all of their values saved by the writeObject method. Multiple objects (say, object1, object2, and object3) can also saved to the same file by calling the writeObject method successively for each object:
objectOut.writeObject(object1);
objectOut.writeObject(object2);
objectOut.writeObject(object3);
Object Deserialization
Writing an object to a file is called serializing the object and reading the object back from a file is called deserializing an object.
An object is deserialised using the following steps:
1. Create a FilelnputStream to read from a file (say,object.dat) and wrap it inside an ObjectInputStream.
2. Invoke the object stream’s readObject method to restore the object’s state from the file object.dat.
Suppose that the AudioPlayer program is rerun again, and a new instance of AudioPlayer called player created. When the player object is deserialized, the audioFile field of player will be initialized to the file played last using the data in the file player.dat:
FilelnputStream fileln = new FilelnputStream(“text/player.dat”);
ObjectlnputStream objectln = new ObjectlnputStream(fileln);
// restore player’s audioFile field by reading it from player.dat
player = (AudioPlayer) objectln.readObject();
objectln.close();
When the object is read using method readObject, it is necessary to cast it back to its original type, as previously shown.
If the object being deserialized has several fields, all of the fields are restored when the readObject method is invoked. Multiple objects can be restored similarly via successive calls to readObject:
object1 = (type) objectln.readObject();
object2 = (type) objectln.readObject();
object3 = (type) objectln.readObject();
The instance player is serialised, as shown in Figure(a), and then deserialised, as shown in Figure (b).
String and array objects can serialise similarly. However, static fields cannot serialise. Java contains a keyword called transient that the programmer can use to specify fields that are not to serialise. Suppose that AudioPlayer has a field called isPlaying that is set to true while the player is playing audio:
transient boolean isPlaying;
This field not saved when the instance player serialised because it declared as transient. When an object deserialised, the transient fields of this object reinitialised to default values (null for objects). For example, the isPlaying field will be reinitialized to false.
The AudioMixer Class: Serialization This section shows how serialisation works using the AudioMixer class. The example used here is similar to the AudioPlayer example, except that we implement the play method in AudioMixer so that it can play audio files. Java contains many classes and interfaces to support playing and recording audio in the javax.sound.sampled package.
The main class in Java’s sound API is the AudioSystemclass. The AudioSystem class contains various static methods that provide information about audio files and resources on your computer for playing sound. Some of these methods shown in Figure below. You can obtain the format of some audio file-say, groovy.way-by using the getAudioFi1eFormat method:
File audioFile = new File(“audio/groovy.wav”);
AudioSystem.getAudioFileFormat(audioFile);
The output is similar to the printSummary method of AudioMixer.
To read the contents of an audio file, convert it to an audio stream using the getAudioInputStream method of AudioSystem:
AudiolnputStream stream = AudioSystem.getAudiolnputStream(audioFile);
Figure shows some methods in class AudiolnputStream. The audio samples can then be read one at a time using the read() method of AudiolnputStream:
stream.read ();
Alternately, several samples can be read into an array using the read (byte[]) method:
byte[] sampleArray = new byte[100];
stream.read(b);
An audio stream can be played by using either a clip or a source data line. A clip is used when the audio data is fully available before playback starts and is not too large. An example of this is a song file stored on a CD. A source data line is used when the data is not fully available before playback starts or when the audio file is too large to be stored in the computer’s memory.
An example of this is a concert being broadcast live. We explain how to use a clip.
You can obtain a clip using the getClip method in AudioSystem.The Clip interface declares several methods to open and playa clip. After the clip is obtained, it can open an audio stream and play it. To open a stream, use an AudiolnputStream as an argument to a clip’s open method. The playback is starid by invoking the start method in Clip, as shown here:
Clip clip = (Clip) AudioSystem.getClip();
clip.open(stream); // open the audio stream
clip.start(); // start playing the audio
After the audio has been played fully, a special type of event called LineEvent.Type.STOP is generated. The clip can add an event handler of type LineListener to listen for this event using the addLineListener method in Clip.
The interface LineListener contains an update method, which must be implemented by the event handler:
void update(LineEvent event)
The LineEvent class contains the method getType to obtain the type of the event that has occurred (such as START or STOP,among others).
With this background, we are now ready to modify AudioMixer so that it can play audio files. Add the field audioFile and the methods play and set-AudioFile to AudioMixer. Also, declare the class to implement the Serializable interface to see how serialization works:
public class AudioMixer implements Serializable {
private File audioFile;
public void setAudioFile(File f){ audioFile = f;}
public void play() throwsException{
AudioInputStream stream = AudioSystem.getAudioInputStream(audioFile);
Clip clip =(Clip) AudioSystem.getClip();
//line listener causes program to exit after play is completed
clip.addLineListener(new LineListener(){
public void update(LineEvent evt){
if(evt.getType()== LineEvent.Type.STOP){
System.exit(0);
}
}
});//open the audio stream and start playing the clip
clip.open(stream);
clip.start();
//program waits here while the music is played
Thread.sleep(1800*1000);
}
}
This statement might appear somewhat strange because we have not yet discussed multithreading.
Thread.sleep(1800*1000);
If you comment out this line, the audio will not be played because the program will exit prematurely. This statement simply says that our program should wait for a specified time interval (1800 seconds in this case), which ensures that the audio is played by the player. If the audio ends before 1800 seconds, a STOP line event is generated, and the line listener attached to the clip handles this event by terminating the program. If you need to play audio fIles longer than 30 minutes, you must make the time interval larger.
Update the main method in AudioMixer as follows to see how serialization works. This example shows how an audio player can be made to restart playing the same audio file that it was playing before it was closed. The code in this method is almost identical to that discussed earlier in class AudioPlayer:
public static void main(String[] args) {
try {
AudioMixer player = new AudioMixer();
System.out.print(“Enter name of file to play:”);
Scanner s = new Scanner(System.in);
String input = s.next();
player.setAudioFile(new File(input));
FileOutputStream fileOut = new FileOutputStream(“text/player.dat”);
ObjectOutputStream objectOut = new ObjectOutputStream(fileOut);
objectOut.writeObject(player);
// serialize
objectOut.close();
System. out. println (“Written AudioMixer object called player to file player.dat”);
System.out.println(“Create new AudioMixer object called player1”);
AudioMixer player1 = new AudioMixer();
System.out.println(“Initialize player1 fields from file player.dat”);
FilelnputStream fileIn = new FileInputStream(“text/player.dat”);
ObjectlnputStream objectln = new ObjectlnputStream(fileln);
player1 = (AudioMixer) objectln.readObject();
// deserialize
objectIn.close();
player1.play(); // play the audio
catch (Exception e) { e.printStackTrace(); }
}
You will also need to add these statements to AudioMixer:
import javax.sound.sampled.*;
import java.util.Scanner;
In main, an AudioMixer instance called player is created and its audioFile field is set to the filename entered by the user on the console. This player is then serialized to the file player.dat by using the writeObject method of objectOut.
After this, player is assigned to a new AudioMixer instance so that its audioFile field becomes null. Then, player1 is deserialized from the file player.dat by calling the readObject method of objectIn, and so audioFile is set to the filename that was previously entered by the user. Finally, p1ayerl plays the audio by calling its play method. When you compile and run the program, you will get the following output:
Enter name of file to play:audio/groovy.wav
Written AudioMixer object called player to file player.dat
Create new AudioMixer object called player1
Initialize player1 fields from file player.dat
The new player named player1 also starts playing the same file groovy.wav.