Представим, что нужно сериализовать объект созданного нами класса расширяющий вообще сторонний класс, который не Serializable, из вообще стороннего jar, который мы скачали откуда-то из интернета.
Ясное дело, поля стороннего класса не сериализуються и не десериализуються.
Externalizable поможет. Благодаря нему можно силой сериализовать и десериализовать поля стороннего класса реализовав методы writeExternal (для сер) и readExternal (для дес).
Пример программы:
import java.io.*;
public class ExternalizableExample {
public static void main(String[] args) {
// сериализуем
try(ObjectOutputStream oos =
new ObjectOutputStream(
new FileOutputStream(“person.dat”)))
{
Person p = new Person(“Sam”, 33, 178, true, 118);
oos.writeObject(p);
}
catch(Exception ex){
System.out.println(ex.getMessage());
}
// десериализация
try(ObjectInputStream ois =
new ObjectInputStream(
new FileInputStream(“person.dat”)))
{
Person p = (Person) ois.readObject();
System.out.printf(
“Name: %s \\t Age: %d \\t Height: %d \\t ” +
“Married: %b \\t IQ: %d \\n”,
p.getName(), p.getAge(), p.getHeight(),
p.getMarried(), p.getIQ());
}
catch(Exception ex){
System.out.println(ex.getMessage());
}
}
}
// представим что типа сторонний класс,
// который не реализует Serializable
// и нет никакой возможности
// сделать его Serializable
// но сериализовать его надо
class MyClass {
private boolean married;
private int IQ;
public MyClass() {}
public MyClass(boolean m, int iq) {
married = m; IQ = iq;
}
boolean getMarried() { return married; }
void setMarried(boolean married) {
this.married = married;
}
int getIQ() { return IQ; }
void setIQ(int IQ) { this.IQ = IQ; }
}
//вместо Serializable – Externalizable
class Person extends MyClass
implements Externalizable{
private String name;
private int age;
private double height;
public Person(){}
public Person(String n, int a, double h,
boolean m, int iq) {
super(m, iq);
name=n; age=a; height=h;
}
String getName() {return name;}
void setName(String name) {this.name = name;}
int getAge() {return age;}
void setAge(int age){this.age = age;}
double getHeight(){return height;}
void setHeight(double height){this.height=height;}
@Override
public void writeExternal(ObjectOutput out)
throws IOException {
//вручную передаем методом сериализации
//(методам writeUTF, writeInt, …)
//поля сериализуемого объекта, которые
//мы хотим сериализовать
out.writeUTF(getName());
out.writeInt(getAge());
out.writeDouble(getHeight());
//это поле из класса, который не Serializable
out.writeBoolean(getMarried());
//и это поле из класса, который не Serializable
out.writeInt(getIQ());
//Но теперь мы можем такие
//поля сериализовать
//вот так вручную так сказать.
//Правда вручную теперь также нужно
//сериализовать и поля класса Person,
//который должен был быть Serializable,
//но он Externalizable по понятным причинам.
}
@Override
public void readExternal(ObjectInput in)
throws IOException, ClassNotFoundException {
//здесь учитывая порядок предшествующей
//сериализации полей в методе выше
//десериализуем их методами десериализации
setName(in.readUTF());
setAge(in.readInt());
setHeight(in.readDouble());
setMarried(in.readBoolean());
setIQ(in.readInt());
}
}
Вывод:
Как видим, сериализуются все поля даже класса, который не Serializable.
Cинглтон это класс, который может иметь только один экземпляр. То есть другой экземпляр этого класса невозможно создать.
Если мы сериализуем этот экземпляр, а потом десериализуем, то получим уже второй экземпляр синглтона, что противоречит идее синглтона.
Чтобы десериализовался тот же экземпляр нужно определить метод readResolve.
Пример программы:
import java.io.*;
//класс синглтон
class Singleton implements Serializable {
public static Singleton instance = new Singleton();
//приватный конструктор нужен
//чтобы невозможно было создавать
//объект данного класса вне класса Singleton.
//то есть выше мы создали один объект
//и больше ни одного создаваться не будет
private Singleton() {
}
// private constructor
//Все что нужно сделать это определить
//в классе синглтона метод с именем readResolve
//вот так как ниже и сериализоваться
//в итоге будет тот же объект синглтона
protected Object readResolve() {
return instance; } //возвращает этот же объект
}
public class SingletonSer {
public static void main(String[] args) {
try {
//снизу обычная сериализация.
//как в прошлых уроках
Singleton instance1 = Singleton.instance;
ObjectOutput out = new ObjectOutputStream(
new FileOutputStream(“file.text”));
out.writeObject(instance1);
out.close();
ObjectInput in = new ObjectInputStream(
new FileInputStream(“file.text”));
Singleton instance2
= (Singleton) in.readObject();
in.close();
//теперь instance1 и instance2 возвращают один
//и тот же hashCode что значит
//что это один и тот же экземпляр
System.out.println(“instance1 hashCode:- ”
+ instance1.hashCode());
System.out.println(“instance2 hashCode:- ”
+ instance2.hashCode());
}
catch (Exception e) {
e.printStackTrace();
}
}
}
Вывод:
Как видим, instance1 и instance2 возвращают один и тот же hashCode, что значит, что это один и тот же экземпляр, что значит, что концепция синглтона не нарушается.
У каждого сериализуемого класса (именно класса, не объекта класса) есть полеSerialVersionUID.
Это числовой идентификатор, который меняется при каждом изменении класса.
Это может быть использовано при сериализации и десериализации.
Допустим у нас есть Serializable класс, объект которого мы сериализовали, а потом а потом в классе этого объекта сделали изменения, и соответственно, изменяется SerialVersionUID класса, и ясное дело десериализовать в объект измененного класса уже не получиться, выйдет ошибка. Чтобы этого избежать можно в сериализуемый класс добавить постоянное статическое значение поля SerialVersionUID, тогда десериализация не выбросит ошибку.
Ясное дело, десериализация будет работать только с полями, которые присутствуют в измененной версии класса, а те поля, которые были в версии класса при сериализации считаться не смогут.
Всё это нужно, чтобы была возможность какое-то время десериализовать класс, который постоянно изменяется.
Давайте, для начала, сериализуем объект класса у которого SerialVersionUID имеет статическое значение.
Пример программы:
import java.io.*;
import java.util.*;
class Person implements Serializable {
// Устанавливаем serialVersionUID в статическое значение 123L.
private static final long serialVersionUID = 123L;
private String name;
private int age;
private double height;
private boolean married;
private int IQ; // это поле будет убрано при десериализации
// для демонстрации пользы статического значения SerialVersionUID
Person(String n, int a, double h, boolean m, int iq) {
name = n;
age = a;
height = h;
married = m;
IQ = iq;
}
String getName() { return name; }
int getAge() { return age; }
double getHeight() { return height; }
boolean getMarried() { return married; }
int getIQ() { return IQ; }
}
public class SerUid {
public static void main(String[] args) {
// Сериализуем
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(“person.dat”))) {
Person p = new Person(“Sam”, 33, 178, true, 10);
// сериализуем класс (у него serialVersionUID = 123L)
oos.writeObject(p);
} catch (Exception ex) {
System.out.println(ex.getMessage());
}
}
}
Скомпилируем и запустим:
Объект сериализован в файл person.dat.
Теперь давайте десериализуем сериализованный объект, но класс Person уже будет изменен.
Также у новой версии класса поле SerialVersionUIDимеет то же значение, что и у прошлой версии класса, что позволяет десериализовать объект, пусть и, ясное дело, частично.
Пример программы:
import java.io.*;
import java.util.*;
class Person implements Serializable {
// все еще 123L как и раньше.
private static final long serialVersionUID = 123L;
private String name;
private int age;
private double height;
private boolean married;
// как видим поля ID больше здесь нет
Person(String n, int a, double h, boolean m) {
name = n;
age = a;
height = h;
married = m;
}
String getName() {
return name;
}
int getAge() {
return age;
}
double getHeight() {
return height;
}
boolean getMarried() {
return married;
}
}
public class SerUid {
public static void main(String[] args) {
// десериализация
try (ObjectInputStream ois = new ObjectInputStream(
new FileInputStream(“person.dat”))) {
// десериализуем объект
// класса с теперь уже удаленным полем IQ
// (serialVersionUID у класса
// десериализуемого объекта
// все еще 123L поэтому считать
// поля которые были в прошлой версии класса,
// то есть все кроме IQ можно)
Person p = (Person) ois.readObject();
System.out.printf(
“Name: %s \t Age: %d \t Height: %f \t Married: %b”,
p.getName(), p.getAge(), p.getHeight(),
p.getMarried());
} catch (Exception ex) {
System.out.println(ex.getMessage());
}
}
}
Как уже было сказано, при сериализации все поля сериализуемого объекта должны быть тоже Serializable.
Если какое-то поле сериализуемого объекта не Serializable и у нас нет доступа к нему для изменения (то есть мы не можем изменить какой-нибудь сторонний реализованный класс и добавить к нему implements Serializable), то можно просто это конкретное поле не сериализоватьобъявив его transient. Тогда будут сериализованы все поля объекта кроме transient поля и это поле будет null при десериализации.
Пример класса который не возможно сделать Serializable:
import java.io.*;
import java.util.*;
// Представим что у нас нет доступа
// к классу SomeNotSerializableClass
// для его изменения, он где-то в другом месте
// и поэтому мы не можем
// дописать ему implements Serializable.
// Но нам нужно сериализовать
// обьект класса Person приведенного ниже
// не смотря на то что в нем есть
// поле класса SomeNotSerializableClass
// который не Serializable
class SomeNotSerializableClass {
private int someField;
SomeNotSerializableClass(int a){
someField=a;
}
int getSomeField(){ return someField;}
}
Пример класса который будет сериализоваться/десериализоваться:
import java.io.*;
import java.util.*;
// это класс будем сериализовать/десериализовать
class Person implements Serializable{
private String name;
private int age;
private double height;
private boolean married;
// поле var ниже это поле класса о котором
// мы говорили выше, который не Serializable.
// Тогда можем просто не сериализовать его
// обьявив его transient, и в сериализованом обьекте
// класса Person поле var будет null.
private transient SomeNotSerializableClass var;
Person(String n, int a, double h, boolean m,
SomeNotSerializableClass var){
name=n; age=a; height=h; married=m; this.var=var;
}
String getName() {return name;}
int getAge() { return age;}
double getHeight(){return height;}
boolean getMarried(){return married;}
SomeNotSerializableClass getNotSerializable(){return var;}
}
public class TransientField {
public static void main(String[] args) {
// здесь обычная сериализация/десериализация
// сериализация
try(ObjectOutputStream oos =
new ObjectOutputStream(new FileOutputStream(“person.dat”)))
{
Person p = new Person(“Sam”, 33, 178, true,
new SomeNotSerializableClass(20));
oos.writeObject(p);
}
catch(Exception ex){
System.out.println(ex.getMessage());
}
// десериализация
try(ObjectInputStream ois =
new ObjectInputStream(new FileInputStream(“person.dat”)))
{
Person p=(Person)ois.readObject();
System.out.printf(
“Name: %s \t Age: %d \t Height: %f \t Married: %b \t “,
p.getName(), p.getAge(), p.getHeight(),
p.getMarried());
System.out.print(“NotSerializableVariable: “);
System.out.print(p.getNotSerializable().getSomeField());
}
catch(Exception ex){
System.out.println(ex.getMessage());
}
}
}
Вывод:
Как видим, объект был сериализован частично. Поле, которое не Serealizable пустое, то есть null.
Что такое сериализация мы уже проходили в разделе потоки ввода/вывода, но повторим, так как это основы сериализации.
ObjectOutputStream – сериалиация.Для сохранения копии объекта в поток (в файл, например) для восстановления его потом (десереализации – ObjectInputStream)
Пример программы:
import java.io.*;
import java.util.*;
public class JustSerialization {
public static void main(String[] args) {
// сериализация
try (ObjectOutputStream oos =
new ObjectOutputStream(new FileOutputStream(“person.dat”)))
{
Person p = new Person(“Sam”, 33, 178, true);
oos.writeObject(p); // сохраняем объект p в файл person.dat
}
catch (Exception ex) {
System.out.println(ex.getMessage());
}
// десериализация
try (ObjectInputStream ois =
new ObjectInputStream(new FileInputStream(“person.dat”)))
{
Person p = (Person) ois.readObject(); // достаем его оттуда
System.out.printf(
“Name: %s \t Age: %d \t Height: %f \t Married: %b \n”,
p.getName(), p.getAge(), p.getHeight(), p.getMarried());
}
catch (Exception ex) {
System.out.println(ex.getMessage());
}
}
}
// класс, объект которого мы сериализуем/десериализуем
class Person implements Serializable {
private String name;
private int age;
private double height;
private boolean married;
Person(String n, int a, double h, boolean m) {
name = n; age = a; height = h; married = m;
}
String getName() { return name; }
int getAge() { return age; }
double getHeight() { return height; }
boolean getMarried() { return married; }
}
Вывод:
Как видим, объект был полностью успешно восстановлен.
Сериализуемый объект должен имплементировать Serializable.
Все его поля тоже должны быть Serializable. Это важно. Простые типы типа int char и т.д. сериализуються и так, сложные же типы должны расширять Serializable.