Статьи

Сериализация Java-объектов с непериализуемыми атрибутами

Существует несколько причин, по которым можно использовать настраиваемую сериализацию вместо того, чтобы полагаться на сериализацию по умолчанию в 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);

outputStringSerializationDemonstrator

Следующие два списка кода предназначены для класса 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);

serializationDemonstratorOnSerializablePersonNonSerializableCityState

В этом случае класс 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);

serializationDemonstratorOnSerializablePersonPlusNonSerializableCityState

Описанный выше подход позволит использовать несериализуемые типы в качестве атрибутов сериализуемых классов без необходимости делать эти поля переходными. Однако если показанный ранее экземпляр 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);

serializedOutputDemoPerson2SerializedCityState

Пользовательская сериализация может использоваться для сериализации класса с атрибутами несериализуемых типов, не делая эти атрибуты несериализуемого типа временными. Это полезный метод, когда сериализуемые классы должны использовать атрибуты типов, которые не сериализуются и которые нельзя изменить.