Serializarea obiectelor

De la WikiLabs
Jump to navigationJump to search

În afară de fluxurile de I/O despre care s-a discutat în detaliu în capitolul de fluxuri, limbajul Java pune la dispoziția programatorilor o noțiune puternică de serializare a obiectelor. Știm deja că o clasă este o structură ce încapsulează date și funcționalitate. Serializarea este procedeul prin care datele încapsulate în instanța unei clase sunt trimise și primite cu ajutorul unui flux de I/O. Clasele care realizează acest lucru sunt java.io.ObjectOutputStream și java.io.ObjectInputStream iar metodele cele mai utilizate sunt writeObject(Object) și readObject().

Regulă: Pentru ca o clasă să poate fi serializată (sau mai bine zis, pentru ca obiectele instanțe ale acelei clase să poată fi serializate), clasa trebuie să implementeze interfața java.io.Serializable:


public class Question implements Serializable{

    public String questionBody;
    public String[] answers;
    public int correctAnswerIndex;

}

Scrierea obiectelor

Un ObjectOutputStream este, ca și clasa FilterOutputStream, un "wrapper", adică o înfășurătoare care are nevoie de un alt OutputStream pentru a funcționa. Rolul clasei ObjectOutputStream este de a transforma un obiect într-un șir de octeți care se scriu mai departe pe stream-ul la nivel de octet:

public class ObjectWriter{

public static void main(String[] _args){
    try{
        OutputStream _byteStream = getSomeOutputStream();
        ObjectOutputStream _objectStream = new ObjectOutputStream(_byteStream);
        Question _q1 = new Question();
        _objectStream.writeObject(_q1);
        _objectStream.close();
    }catch(IOException _ioe){
        System.out.println("Unable to write object: " + _ioe.getMessage());
    }
}

public static OutputStream getSomeOutputStream(){
    //...
}

}

Câmpurile statice nu sunt serializate (serializarea se referă la obiect și nu la clasă). Dacă obiectul serializat conține referințe la alte obiecte, acestea sunt și ele serializate automat urmând aceleaşi reguli.

În plus, pentru a proteja un câmp la serializare, se poate folosi modificatorul transient. Câmpurile transient nu se serializează:

public class Question implements Serializable{

    public String questionBody;
    public String[] answers;
    public transient int correctAnswerIndex;

}

Citirea obiectelor

Citirea obiectelor serializate se face utilizând un obiect de tip ObjectInputStream care se folosește de un InputStream generic. După cum spuneam mai sus, un obiect serializat este de fapt un șir de octeți ce reprezintă valorile fiecărui câmp (ne-static și ne-transient) al clasei. Acesta nu conține și informații despre clasă, prin urmare, ca un obiect să poată fi deserializat, aplicația care face deserializarea trebuie să aiba la dispoziție clasa a cărei instanță este obiectul deserializat. Dacă această clasă nu este disponibilă, atunci metoda readObject() va arunca o excepție de tip java.lang.ClassNotFoundException.

Verificarea clasei este făcută la runtime (în timpul execuției programului, la momentul apelării metodei readObject()) și implică compararea unui câmp static final long serialVersionUID declarat în clasă, cu valoarea conținută de obiectul serializat. Prin urmare, nu este suficient ca două clase sa fie identice din punct de vedere al sursei Java, ca să fie compatibile la serializare, trebuie să aibă aceeași valoare a câmpului serialVersionUID. Dacă nu este specificat de către programator, valoarea acestui câmp este generată aleator de compilator la compilarea clasei și va diferi pentru același cod compilat pe două mașini diferite:

public class Question implements Serializable{

    static final long serialVersionUID = 0x0000CAFEBABE0000;

    public String questionBody;
    public String[] answers;
    public int correctAnswerIndex;

}

Totuși, dacă două clase au aceeași valoare pentru câmpul serialVersionUID, dar ele diferă ca implementare, atunci metoda readObject() va arunca o excepție de tip java.io.InvalidClassException semnalând că există o problemă de compatibilitate între clasa disponibilă și obiectul serializat. Toate aceste excepții sunt extinse din clasa java.io.ObjectStreamException.

Pentru a putea fi compatibilă cu orice tip de obiect, metoda readObject() din clasa ObjectInputStream întoarce o referință la un obiect de tip Object. Astfel, pentru a-l putea folosi în aplicații reale, programatorul trebuie să facă un up-cast explicit:

public class ObjectReader{

public static void main(String[] _args){
    try{
        InputStream _byteStream = getSomeInputStream();
        ObjectInputStream _objectStream = new ObjectIntputStream(_byteStream);
        Question _q1;
        _q1 = (Question)_objectStream.readObject();
        _objectStream.close();
    }catch(ObjectStreamException _ose){
        // ObjectStreamException first since it inherits IOException
        System.out.println("Unable to deserialize (Object stream problem): " + _ose.getMessage());
    }catch(IOException _ioe){
        System.out.println("Unable to deserialize (Byte stream problem): " + _ioe.getMessage());
    }
}

public static OutputStream getSomeOutputStream(){
    //...
}

}