IO - serialization  
Introduction

Serialization is the conversion of the state of an object into a byte stream; deserialization does the opposite. Stated differently, serialization is the conversion of a Java object into a static stream (sequence) of bytes, which we can then save to a database or transfer over a network. The serialization process is instance-independent; for example, we can serialize objects on one platform and deserialize them on another. Classes that are eligible for serialization need to implement a special marker interface, Serializable.

Both ObjectInputStream and ObjectOutputStream are high level classes that extend java.io.InputStream and java.io.OutputStream, respectively. ObjectOutputStream can write primitive types and objects to an OutputStream as a stream of bytes. We can then read these streams using ObjectInputStream.

The most important method in ObjectOutputStream is:

    public final void writeObject(Object o) throws IOException;

This method takes a serializable object and converts it into a sequence (stream) of bytes. Similarly, the most important method in ObjectInputStream is:

    public final Object readObject()  throws IOException, ClassNotFoundException;

This method can read a stream of bytes and convert it back into a Java object. It must then be cast back to the original object.

Serial Version UID

A version number is associated with each serializable class. It is used it to verify that the saved and loaded objects have the same attributes and thus are compatible on serialization. InvalidClassException is generated if saved and loaded objects have a different serial version UID.

If a serializable class doesn’t declare a serialVersionUID, the JVM will generate one automatically at run-time. However, it’s highly recommended that each class declares its serialVersionUID, as the generated one is compiler dependent and thus may result in unexpected InvalidClassExceptions.


Example - writing objects in a file (persons with name and age read and written in the file save.dat) 


import java.io.Serializable;
public class Pers implements Serializable{
    private static final long serialVersionUID = 1L;
    String name;
    int age;
    Pers(String name,int age){
        this.name = name;
        this.age = age;
    }
}


import java.io.*;
public class ArrPers implements Serializable{
    private static final long serialVersionUID = 1L;
    int size;
    Pers arr[];
    ArrPers(){
        arr = new Pers[5];
        size = 0;
    }
    void add(Pers p){
        if(size >= arr.length-1){
            Pers temp[] = new Pers[2*arr.length];
            System.arraycopy(arr, 0, temp, 0, arr.length);
            arr=temp;
        }
        arr[size++]=p;
    }
    void prt(){
        System.out.println ("\nArrpers contains: "+size+" objets");
        for(int i=0;i<this.size;i++){
            System.out.println("name: "+arr[i].name+"\tage:"+ arr[i].age);
        }
    }
    void remove (Pers p){
        for(int i=0;i<size;i++){
            if (p==arr[i]){
                arr[i]=arr[--size];
                break;
            }
        }
    }
    void save(){
        try{
            FileOutputStream fos = new FileOutputStream ("save.dat");
            ObjectOutputStream oos = new ObjectOutputStream (fos);
            oos.writeObject(this);
            oos.close();           
        }
        catch(Exception ex){
            System.out.println("the file save.dat cannot be created");
            return;
        }       
    }
    static ArrPers load(){
        ArrPers p= null;
        try{
            FileInputStream fis = new FileInputStream ("save.dat");
            ObjectInputStream ois = new ObjectInputStream (fis);
            p= (ArrPers)ois.readObject();   
            ois.close();
        }
        catch(Exception ex){
            System.out.println("the file save.dat is not found");
        }       
        return p;
    }
}

public class Test {
    public static void main(String arg[]){
        ArrPers p =null;
        p= ArrPers.load();
        if(p == null) {
            p = new ArrPers();
            System.out.println("New array is created");
        }
        else {
            System.out.println("The array is loaded:");
        }
        p.prt();
        System.out.println("\nAdding three persons to the array");
        for(int i=0;i<3;i++){
            String name ="client"+(int)(Math.random()*1000);
            int age = (int)(Math.random()*80);
            p.add(new Pers(name,age));
        }
        p.prt();
        p.save();
    }
}


Exercise: Modify the class Test  to ask whether to keep the old file or add 3 new persons in the array.

Non serializable fields


Static fields belong to a class (not to an object) and are not serialized. Also, it is possible to  use the keyword transient to ignore fields during serialization:


   class MyClass implements Serializable {
    transient Thread thread;  //Skip serialization of the transient field
    transient String fieldIdontwantSerialization;
    static int number; //Skip serialization of the statis field
         // Serialize the rest of the fields
    int data;
    String x;
         // More code
}




Inheritance and Composition

When a class implements the java.io.Serializable interface, all its derivates classes are serializable as well. Conversely, when an object field has a reference to another object, both classes must implement the Serializable interface separately, or else a NotSerializableException will be thrown



import java.io.Serializable;
public class PersonSr implements Serializable{
     private static final long serialVersionUID = 1L;
    static int num=0;
    String name;
    String id;
    PersonSr(){
        name = "student "+ ++num;
        id = (int)(Math.random()*3999)+1111+"";
    }
}   
public class StudentSr extends PersonSr{
     private static final long serialVersionUID = 1L;
    int notes[];
    StudentSr(){
        notes = new int [5];
        for(int i =0;i<notes.length;i++){
            notes[i]=(int)(Math.random()*5)+2;
        }
    }
    public String toString(){
       String rez;
            rez = "name:"+name+"\tid:"+id+"\tnotes:";
        for(int i=0;i<notes.length;i++){
            rez += "\t"+notes[i];
        }
        return rez;       
    }
}



Serialisation:

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
public class StudWr {
    public static void main(String arg[]) throws IOException{
        ObjectOutputStream oos = null;
        StudentSr s;
        try{
            oos = new ObjectOutputStream (
                new FileOutputStream ("save.ser"));
            for(int i=0;i<9;i++){
                s =new StudentSr();
                System.out.print(""+s);
                oos.writeObject(s);
                System.out.println("       # is serialized");
            }
        }
        finally{
            oos.close();
        }
    }
}


Deserialisation

import java.io.EOFException;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.FileNotFoundException;
import java.io.ObjectInputStream;
public class StudRd {
    public static void main(String[] arg)throws
             IOException,ClassNotFoundException{
        ObjectInputStream ois = null;
        StudentSr s;
        int n=0, nf=0;
        try{
            ois = new ObjectInputStream (
                new FileInputStream ("save.ser"));
            for(;;){
                s= (StudentSr)ois.readObject();
                n++;
                if(average(s)>=4){
                    System.out.print(""+s);
                    System.out.println("\thas average "+ average(s));
                    nf++;
                }
            }
        }
        catch (FileNotFoundException fne) {
            System.out.println("There is no file save.ser");
        }
        catch(EOFException ex){
            System.out.println("printed "+nf+" students, found total "+n+" students");
        }
        finally{
            if(ois!=null)ois.close();
        }
    }
    public static double average(StudentSr s){
        double sum;
        int i;
        for(sum=i=0;i<s.notes.length;i++){
            sum+=s.notes[i];
        }
        return sum/s.notes.length;
    }
}



Custom Serialization

The Java classes can override the default serialization behavior. Custom serialization can be particularly useful when trying to serialize an object that has some not serializable attributes.  Or the class is expected to be changed in future releases which could break the deserialization of previously serialized objects.

In most cases, when we will customize java serialization, you will write the fields one by one – in a sequence. It is the most common way to override default java serialization process.

Two methods in the class to be serialized must be redefined:

    private void writeObject(ObjectOutputStream out) throws IOException{
           // fields serialization, using methds in ObjectOuputStream class
    }

and

    private void readObject(ObjectInputStream in)   throws IOException, ClassNotFoundException {
           //
 // fields deserialization, using methds in ObjectInputStream class
    }

The sequence of class fields in read and write methods MUST BE the same.

It is possible to use defaultReadObject() and defaultWriteObject() inside readObject() and writeObject() methods – to enable default serialization and deserialization.

Working with text files


The тext information can be processed by letters or by lines.

Exemple - read and write a text file (copy a text file - The input file and the outpute file, are given as  arguments )

import java.io.*;
public class Copy {
   public static void main(String arg[]) {
      String f1="",f2="";
      try{
          f1=arg[0];
          f2=arg[1];
      }catch (IndexOutOfBoundsException e){
          System.out.println("Usage: java Copy file_1 file_2");
          System.exit(1);
      }
      try { 
         BufferedReader in = new BufferedReader(
                              new FileReader(f1));
         BufferedWriter ot = new BufferedWriter(
                 new FileWriter(f2));
        
         System.out.println("Copy "+arg[0]+" as "+arg[1]);
      
         String tampon="" ;
         while(( tampon=in.readLine()) != null) {
             ot.write(tampon) ;
             ot.newLine();    
         }
         in.close();
         ot.close();
         System.out.println("done");
      }
      catch(FileNotFoundException ef){
          System.out.print(arg[0]+" does not exist");
          System.out.println(" or "+arg[1]+ " can not be created");
          System.exit(3);
      }
      catch(IOException e) {
          e.printStackTrace();
          System.exit(4);
      }

    }
}


Working with binary files

Exemple - read and write a file binary

To test download in the project directory one small file -  eg   icon.png  from https://ff.tu-sofia.bg/JTech/go-back-icon.png
and one larger file - eg test.mp4  from https://jsoncompare.org/LearningContainer/SampleFiles/Video/MP4/Sample-MP4-Video-File-Download.mp4

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
public class FCopy {
public static void main(String arg[]) throws IOException{
int bt;
if(arg.length!=2){
System.out.println("Usage: FCopy fileSource fileDestination");
return;
}
String fns = arg[0], fnd = arg[1];
System.out.println("copy the file "+fns+" in the file "+fnd);
FileInputStream fi = null;
FileOutputStream fo = null;
try {
fi = new FileInputStream(fns);
fo = new FileOutputStream(fnd);
while((bt = fi.read())!=-1)fo.write(bt);
System.out.println("file "+fns+" is copied as "+fnd);
}
catch(FileNotFoundException ex){
System.out.println("The file "+fns+" does not exist");
}
finally{
if(fi != null)fi.close();
if(fo != null)fo.close();
}
}
}

Using a buffer

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
public class FCopyArr {
public static void main(String arg[]) throws IOException{
byte bt[] = new byte[2048];
int len;
if(arg.length!=2){
System.out.println("Usage: FCopy fileSource fileDestination");
return;
}
String fns = arg[0], fnd = arg[1];
System.out.println("copy the file "+fns+" in the file "+fnd);
FileInputStream fi = null;
FileOutputStream fo = null;
try {
fi = new FileInputStream(fns);
fo = new FileOutputStream(fnd);
while((len = fi.read(bt))!=-1){
fo.write(bt,0,len);
}
System.out.println("file "+fns+" is copied as "+fnd);
}
catch(FileNotFoundException ex){
System.out.println("The file "+fns+" does not exist");
}
finally{
if(fi != null)fi.close();
if(fo != null)fo.close();
}
}
}