Существует несколько причин, по которым можно использовать настраиваемую сериализацию вместо того, чтобы полагаться на сериализацию по умолчанию в Java. Одна из наиболее распространенных причин — повышение производительности, но другая причина написания настраиваемой сериализации — когда механизм сериализации по умолчанию не поддерживается. В частности, как будет показано в этом посте, можно использовать пользовательскую сериализацию, чтобы позволить сериализовать более крупный объект, даже если атрибуты этого объекта сами по себе не сериализуются напрямую .
В следующем листинге кода показан простой класс для сериализации заданного класса в файл с указанным именем и десериализации объекта из этого же файла. Я буду использовать его в этом посте для демонстрации сериализации.
SerializationDemonstrator.java
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
|
package dustin.examples.serialization;import static java.lang.System.out;import java.io.FileInputStream;import java.io.FileOutputStream;import java.io.IOException;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;/** * Simple serialization/deserialization demonstrator. * * @author Dustin */public class SerializationDemonstrator{ /** * Serialize the provided object to the file of the provided name. * @param objectToSerialize Object that is to be serialized to file; it is * best that this object have an individually overridden toString() * implementation as that is used by this method for writing our status. * @param fileName Name of file to which object is to be serialized. * @throws IllegalArgumentException Thrown if either provided parameter is null. */ public static <T> void serialize(final T objectToSerialize, final String fileName) { if (fileName == null) { throw new IllegalArgumentException( "Name of file to which to serialize object to cannot be null."); } if (objectToSerialize == null) { throw new IllegalArgumentException("Object to be serialized cannot be null."); } try (FileOutputStream fos = new FileOutputStream(fileName); ObjectOutputStream oos = new ObjectOutputStream(fos)) { oos.writeObject(objectToSerialize); out.println("Serialization of Object " + objectToSerialize + " completed."); } catch (IOException ioException) { ioException.printStackTrace(); } } /** * Provides an object deserialized from the file indicated by the provided * file name. * * @param <T> Type of object to be deserialized. * @param fileToDeserialize Name of file from which object is to be deserialized. * @param classBeingDeserialized Class definition of object to be deserialized * from the file of the provided name/path; it is recommended that this * class define its own toString() implementation as that will be used in * this method's status output. * @return Object deserialized from provided filename as an instance of the * provided class; may be null if something goes wrong with deserialization. * @throws IllegalArgumentException Thrown if either provided parameter is null. */ public static <T> T deserialize(final String fileToDeserialize, final Class<T> classBeingDeserialized) { if (fileToDeserialize == null) { throw new IllegalArgumentException("Cannot deserialize from a null filename."); } if (classBeingDeserialized == null) { throw new IllegalArgumentException("Type of class to be deserialized cannot be null."); } T objectOut = null; try (FileInputStream fis = new FileInputStream(fileToDeserialize); ObjectInputStream ois = new ObjectInputStream(fis)) { objectOut = (T) ois.readObject(); out.println("Deserialization of Object " + objectOut + " is completed."); } catch (IOException | ClassNotFoundException exception) { exception.printStackTrace(); } return objectOut; }} |
Следующий листинг кода иллюстрирует использование класса SerializationDemonstrator для сериализации и десериализации стандартной строки Java (которая является сериализуемой). Снимок экрана следует за листингом кода и показывает вывод (в NetBeans) запуска этой строки через методы serialize и deserialize класса SerializationDemonstrator .
Запуск методов SerializationDemonstrator для строки
|
1
2
|
SerializationDemonstrator.serialize("Inspired by Actual Events", "string.dat");final String stringOut = SerializationDemonstrator.deserialize("string.dat", String.class); |
Следующие два списка кода предназначены для класса Person.java и класса, который он имеет в качестве типа атрибута ( CityState.java ). Хотя Person реализует Serializable , класс CityAndState этого не делает.
Person.java
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
package dustin.examples.serialization;import java.io.Serializable;/** * Person class. * * @author Dustin */public class Person implements Serializable{ private String lastName; private String firstName; private CityState cityAndState; public Person( final String newLastName, final String newFirstName, final CityState newCityAndState) { this.lastName = newLastName; this.firstName = newFirstName; this.cityAndState = newCityAndState; } public String getFirstName() { return this.firstName; } public String getLastName() { return this.lastName; } @Override public String toString() { return this.firstName + " " + this.lastName + " of " + this.cityAndState; }} |
CityAndState.java
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
package dustin.examples.serialization;/** * Simple class storing city and state names that is NOT Serializable. * * @author Dustin */public class CityState{ private final String cityName; private final String stateName; public CityState(final String newCityName, final String newStateName) { this.cityName = newCityName; this.stateName = newStateName; } public String getCityName() { return this.cityName; } public String getStateName() { return this.stateName; } @Override public String toString() { return this.cityName + ", " + this.stateName; }} |
В следующем листинге кода показано выполнение SerializationDemonstrator для сериализуемого класса Person с несериализуемым CityState . За списком кода следует снимок экрана с выводом в NetBeans.
Запуск методов SerializationDemonstrator для сериализуемого лица с несериализуемым CityState
|
1
2
3
4
|
final Person personIn = new Person("Flintstone", "Fred", new CityState("Bedrock", "Cobblestone"));SerializationDemonstrator.serialize(personIn, "person.dat");final Person personOut = SerializationDemonstrator.deserialize("person.dat", Person.class); |
В этом случае класс CityState — это мой собственный класс, и я мог бы сделать его сериализуемым. Однако, предположив, что этот класс был частью стороннего фреймворка или библиотеки, и я не смог изменить сам класс, я могу изменить Person чтобы использовать настраиваемую сериализацию и десериализацию и правильно работать с CityState . Это показано в следующем листинге кода для класса SerializablePerson , адаптированного из Person .
SerializablePerson.java
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
|
package dustin.examples.serialization;import java.io.IOException;import java.io.InvalidObjectException;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.io.ObjectStreamException;import java.io.Serializable;/** * Person class. * * @author Dustin */public class SerializablePerson implements Serializable{ private String lastName; private String firstName; private CityState cityAndState; public SerializablePerson( final String newLastName, final String newFirstName, final CityState newCityAndState) { this.lastName = newLastName; this.firstName = newFirstName; this.cityAndState = newCityAndState; } public String getFirstName() { return this.firstName; } public String getLastName() { return this.lastName; } @Override public String toString() { return this.firstName + " " + this.lastName + " of " + this.cityAndState; } /** * Serialize this instance. * * @param out Target to which this instance is written. * @throws IOException Thrown if exception occurs during serialization. */ private void writeObject(final ObjectOutputStream out) throws IOException { out.writeUTF(this.lastName); out.writeUTF(this.firstName); out.writeUTF(this.cityAndState.getCityName()); out.writeUTF(this.cityAndState.getStateName()); } /** * Deserialize this instance from input stream. * * @param in Input Stream from which this instance is to be deserialized. * @throws IOException Thrown if error occurs in deserialization. * @throws ClassNotFoundException Thrown if expected class is not found. */ private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException { this.lastName = in.readUTF(); this.firstName = in.readUTF(); this.cityAndState = new CityState(in.readUTF(), in.readUTF()); } private void readObjectNoData() throws ObjectStreamException { throw new InvalidObjectException("Stream data required"); }} |
Приведенный выше листинг кода показывает, что SerializablePerson имеет собственные методы writeObject и readObject для поддержки настраиваемой сериализации / десериализации, которые соответствующим образом обрабатывают свой атрибут CityState типа CityState . Фрагмент кода для запуска этого класса через SerializationDemonstrator и успешные результаты этого показаны ниже.
Запуск SerializationDemonstrator на SerializablePerson
|
1
2
3
4
|
final SerializablePerson personIn = new SerializablePerson("Flintstone", "Fred", new CityState("Bedrock", "Cobblestone"));SerializationDemonstrator.serialize(personIn, "person.dat");final SerializablePerson personOut = SerializationDemonstrator.deserialize("person.dat", SerializablePerson.class); |
Описанный выше подход позволит использовать несериализуемые типы в качестве атрибутов сериализуемых классов без необходимости делать эти поля переходными. Однако если показанный ранее экземпляр CityState необходимо использовать в нескольких сериализуемых классах, может быть лучше украсить класс CityState сериализуемым декоратором, а затем использовать этот сериализованный класс декоратора в классах, которые необходимо сериализовать. Следующий листинг кода показывает SerializableCityState который украшает CityState сериализованной версией.
SerializableCityState
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
|
package dustin.examples.serialization;import java.io.IOException;import java.io.InvalidObjectException;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.io.ObjectStreamException;import java.io.Serializable;/** * Simple class storing city and state names that IS Serializable. This class * decorates the non-Serializable CityState class and adds Serializability. * * @author Dustin */public class SerializableCityState implements Serializable{ private CityState cityState; public SerializableCityState(final String newCityName, final String newStateName) { this.cityState = new CityState(newCityName, newStateName); } public String getCityName() { return this.cityState.getCityName(); } public String getStateName() { return this.cityState.getStateName(); } @Override public String toString() { return this.cityState.toString(); } /** * Serialize this instance. * * @param out Target to which this instance is written. * @throws IOException Thrown if exception occurs during serialization. */ private void writeObject(final ObjectOutputStream out) throws IOException { out.writeUTF(this.cityState.getCityName()); out.writeUTF(this.cityState.getStateName()); } /** * Deserialize this instance from input stream. * * @param in Input Stream from which this instance is to be deserialized. * @throws IOException Thrown if error occurs in deserialization. * @throws ClassNotFoundException Thrown if expected class is not found. */ private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException { this.cityState = new CityState(in.readUTF(), in.readUTF()); } private void readObjectNoData() throws ObjectStreamException { throw new InvalidObjectException("Stream data required"); }} |
Этот сериализуемый декоратор может быть использован непосредственно в классе Person и этот включающий Person может использовать сериализацию по умолчанию, поскольку все его поля сериализуемы. Это показано в следующем листинге кода для Person2 адаптированного из Person .
Person2.java
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
package dustin.examples.serialization;import java.io.Serializable;/** * Person class. * * @author Dustin */public class Person2 implements Serializable{ private final String lastName; private final String firstName; private final SerializableCityState cityAndState; public Person2( final String newLastName, final String newFirstName, final SerializableCityState newCityAndState) { this.lastName = newLastName; this.firstName = newFirstName; this.cityAndState = newCityAndState; } public String getFirstName() { return this.firstName; } public String getLastName() { return this.lastName; } @Override public String toString() { return this.firstName + " " + this.lastName + " of " + this.cityAndState; }} |
Этот код может быть выполнен, как показано в следующем листинге кода, за которым следует его вывод, как показано в окне вывода NetBeans.
Запуск SerializationDemonstrator против Person2 / SerializableCityState
|
1
2
3
4
|
final Person2 personIn = new Person2("Flintstone", "Fred", new SerializableCityState("Bedrock", "Cobblestone"));SerializationDemonstrator.serialize(personIn, "person.dat");final Person2 personOut = SerializationDemonstrator.deserialize("person.dat", Person2.class); |
Пользовательская сериализация может использоваться для сериализации класса с атрибутами несериализуемых типов, не делая эти атрибуты несериализуемого типа временными. Это полезный метод, когда сериализуемые классы должны использовать атрибуты типов, которые не сериализуются и которые нельзя изменить.
