Сериализация объектов в Java

Кроме данных базовых типов, в поток можно отправлять объекты классов.

Процесс преобразования объектов в потоки байтов для хранения называется сериализацией. Процесс извлечения объекта из потока байтов называется десериализацией. Существует два способа сделать объект сериализуемым.

Для того чтобы объекты класса могли быть подвергнуты процессу сериализации, этот класс должен расширять интерфейс Serializable. Все подклассы такого класса также будут сериализованы. Многие стандартные классы реализуют этот интерфейс. Этот процесс заключается в сериализации каждого поля объекта, но только в том случае, если это поле не имеет спецификатора static или transient. Спецификаторы transient и static означают, что поля, помеченные ими, не могут быть предметом сериализации, но существует различие в десериализации. Так, поле со спецификатором transient после десериализации получает значение по умолчанию, соответствующее его типу (объектный тип всегда инициализируется по умолчанию значением null), а поле со спецификатором static получает значение по умолчанию в случае отсутствия в области видимости объектов своего типа, а при их наличии получает значение, которое определено для существующего объекта.

Интерфейс Serializable не имеет методов, которые необходимо реализо­вать, поэтому его использование ограничивается упоминанием при объявлении класса. Все действия в дальнейшем производятся по умолчанию. Для записи объектов в поток необходимо использовать класс ObjectOutputStream. После этого достаточно вызвать метод writeObject(Object ob) этого класса для сериализации объекта ob и пересылки его в выходной поток данных. Для чтения используется соответственно класс ObjectInputStream и его метод readObject(), возвращающий ссылку на класс Object. После чего следует преобразовать полученный объект к нужному типу.

Необходимо знать, что при использовании Serializable десериализация происходит следующим образом: под объект выделяется память, после чего его поля заполняются значениями из потока. Конструктор объекта при этом не вызывается.

/* пример # 7 : запись сериализованного объекта в файл и его десериализация : Student.java : DemoSerialization.java */

package chapt09;

import java.io.*;

class Student implements Serializable {

protected static String faculty;

private String name;

private int id;

private transient String password;

private static final long serialVersionUID = 1L;

/*значение этого поля для класса будет дано далее*/

public Student(String nameOfFaculty, String name,

int id, String password){

faculty = nameOfFaculty;

this.name = name;

this.id = id;

this.password = password;

}

public String toString(){

return "\nfaculty " + faculty + "\nname " + name

+ "\nID " + id + "\npassword " + password;

}

}

public class DemoSerialization {

public static void main(String[] args) {

// создание и запись объекта

Student goncharenko =

new Student("MMF", "Goncharenko", 1, "G017s9");

System.out.println(goncharenko);

File fw = new File("demo.dat");

try {

ObjectOutputStream ostream =

new ObjectOutputStream(

new FileOutputStream(fw));

ostream.writeObject(goncharenko);

ostream.close();

} catch (IOException e) {

System.err.println(e);

}

Student.faculty = "GEO";//изменение значения static-поля

// чтение и вывод объекта

File fr = new File("demo.dat");

try {

ObjectInputStream istream =

new ObjectInputStream(

new FileInputStream(fr));

Student unknown =

(Student)istream.readObject();

istream.close();

System.out.println(unknown);

} catch (ClassNotFoundException ce) {

System.err.println(ce);

System.err.println("Класс не существует");

} catch (FileNotFoundException fe) {

System.err.println(fe);

System.err.println("Файл не найден");

} catch (IOException ioe) {

System.err.println(ioe);

System.err.println("Ошибка доступа");

}

}

}

В результате выполнения данного кода в консоль будет выведено:

faculty MMF

name Goncharenko

ID 1

password G017s9

faculty GEO

name Goncharenko

ID 1

password null

В итоге поля name и id нового объекта unknown сохранили значения, которые им были присвоены до записи в файл. Полe passwоrd со спецификатором transient получило значение по умолчанию, соответствующее типу (объектный тип всегда инициализируется по умолчанию значением null). Поле faculty, помеченное как статическое, получает то значение, которое имеет это поле на текущий момент, то есть при создании объекта goncharenko поле получило значение MMF, а затем значение статического поля было изменено на GEO. Если же объекта данного типа нет в области видимости, то статическое поле также получает значение по умолчанию.

Если поля класса являются объектами другого класса, то необходимо, чтобы тот класс тоже реализовал интерфейс Serializable.

При сериализации объекта класса, реализующего интерфейс Serializable, учитывается порядок объявления полей в классе. Поэтому при изменении порядка десериализация пройдет некорректно. Это обусловлено тем, что в каждый класс, реализующий интерфейс Serializable, на стадии компиляции добавляется поле private static final long serialVersionUID. Это поле содер­жит уникальный идентификатор версии сериализованного класса. Оно вычисляется по содержимому класса – полям, их порядку объявления, методам, их порядку объявления.

Это поле записывается в поток при сериализации класса. Это единственный случай, когда static-поле сериализуется.

При десериализации значение этого поля сравнивается с имеющимся у класса в виртуальной машине. Если значения не совпадают, инициируется исключение java.io.InvalidClassException. Соответственно, при любом изменении в классе это поле поменяет свое значение.

Если набор полей класса и их порядок жестко определены, методы класса могут меняться. В этом случае сериализации ничего не угрожает, однако стандарт­ный механизм не даст десериализовать данные. В таких случаях можно вручную
в классе определить поле private static final long serialVersionUID.

Вместо реализации интерфейса Serializable можно реализовать Externalizable, который содержит два метода:

void writeExternal(ObjectOutput out)

void readExternal(ObjectInput in)

При использовании этого интерфейса в поток автоматически записывается только идентификация класса. Сохранить и восстановить всю информацию о состоянии экземпляра должен сам класс. Для этого в нем должны быть переопределены методы writeExternal() и readExternal() интерфейса Externalizable. Эти методы должны обеспечить сохранение состояния, описываемого полями самого класса и его суперкласса.

При восстановлении Externalizable-объекта экземпляр создается путем вызова конструктора без аргументов, после чего вызывается метод
readExternal(), поэтому необходимо проследить, чтобы в классе был пустой конструктор. Для сохранения состояния вызываются методы ObjectOutput, с помощью которых можно записать как примитивные, так и объектные значения. Для корректной работы в соответствующем методе readExternal() эти значения должны быть считаны в том же порядке.

Для чтения и записи в поток значений отдельных полей объекта можно использовать соответственно методы внутренних классов:

ObjectInputStream.GetField

ObjectOutputStream.PutField.

Вы можете следить за любыми ответами на эту запись через RSS 2.0 ленту. Вы можете оставить ответ, или trackback с вашего собственного сайта.

Оставьте отзыв

XHTML: Вы можете использовать следующие теги: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

 
Rambler's Top100