Writing instances of Classes to file

Previously we had explored reading and writing data in plain text files to disk. This works, but requires the developer to handle the processing of each line or token of input, which is rather tedious. It becomes much more challenging if we wish to handle, for example, lists of instances of objects, which could themselves contain other objects.

Serializable

Fortunately Java makes it very easy to write instances of classes to disk, we simply implement the Serializable interface. The serializable interface does not require the implementation of any methods (it simply acts as a flag), to all that is required is to add implements Serializable after the class declaration and add the appropriate import statement An example is shown here:

package com.tinyappco;
import java.io.Serializable;
class MyClass implements Serializable {
    
    //class implementation goes here
    
}
Provided any classes that comprise the class also implement Serializable, then it can be written to file. Note that if you try to write an object to disk and the class or a member class does not implement Serializable, then a NotSerializableException will be thrown

Collections

ArrayList, LinkedList, HashSet and HashMapas well as some other implementations of Collection implement Serializable, so provided the objects which any of those collection types contains also implement Serializable, then an Collection of objects can be easily written to file (which avoids saving objects one at a time)

Writing to disk

Once we've implemented Serializable in the class (and any compositional classes), the process to write it to disk is relatively simple. We import the java.io.* packages and

  1. Create a FileOutputStream object pointing to the location of the file we wish to write to on disk (this will be created if it does not exist)
  2. Create an ObjectOutputStream which we pass the FileOutputStream object to
  3. Call writeObject on the ObjectOutputStream object and pass in the object we wish to write to file
Because each of these stages could potentially throw an exception (e.g. if the file location cannot be written to or the object does not implement Serializable), then we must surround the code in a try block and handle errors in a catch block. The example code to implement the saving of an Odometer (from previous tutorials) to file would appear as follows:
//code assumes odo is an instance of Odometer class which implements Serializable
try {
    FileOutputStream fileOutputStream = new FileOutputStream("save.dat");
    ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
    objectOutputStream.writeObject(odo);
}
catch (Exception ex){
    System.out.println("Can't write data to file");
}
Note that the file will not be readable in text format should you try and open it

Reading from disk

The process for reading from file is farily similar, however instead of Output stream objects, we create input stream objects. Example code to do this is a follows:

try {
    FileInputStream fileInputStream = new FileInputStream("save.dat");
    ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
    Odometer odoFromFile = (Odometer)objectInputStream.readObject();
}
catch (Exception ex){
    System.out.println("error reading from file or restoring data");
}

One thing that is important to be aware of is that we have to explicitly declare the type of object being read by the ObjectInputStream instance. This is because the method can read any type of object. To do this, we specify the type, in brackets, immediatly beore the object's method class e.g. (Odometer)objectInputStream.readObject();. This is known as 'casting' and tells the computer what type of object something is, when it is unable to determine this itself.

Task

The Poketutor game restarts everytime the application is launched - all the captured PokeTutors are lost and the player starts again. Implement a feature so that they players game state is automatically saved if they choose to exit the game, and ensure that their previous game state (if it exists) is loaded. Remember that the first time the player starts the game there will be no file, so you should test for this be deleting the file before launching the app once you have implemented the save and load functionality.