Locked History Actions

java/writeReplace

writeReplace/readResolve

writeReplaceは言わば、書き込まれる(予定の)オブジェクト自身が「自分の代わりにあいつを書き込んでくれ」という指定である。以下にサンプルを示す。

package test;

import java.io.*;

public class SerialTest {

  public static class TestObject implements Serializable {
    int value;
    public TestObject(int value) {
      this.value = value;
    }
    @Override public String toString() {
      return "TestObject " + value;
    }
    
    /** 書き込み時、自身の代わりにReplacedObjectを直列化せよ、という指定 */
    public Object writeReplace() throws ObjectStreamException {
      return new ReplacedObject(value);
    }    
  }
  
  public static class ReplacedObject implements Serializable {
    int value;
    public ReplacedObject(int value) {
      this.value = value;
    }
    @Override public String toString() {
      return "ReplacedObject " + value;
    }
    
    /** 読込み時、自身の代わりにTestObjectを返せ、という指定 */
//    public Object readResolve() throws ObjectStreamException {
//      return new TestObject(value);
//    }
  }
  
  private static byte[]serialize(Object object) throws Exception  {
    ByteArrayOutputStream bout = new ByteArrayOutputStream();
    ObjectOutputStream oout = new ObjectOutputStream(bout);    
    try {
      oout.writeObject(object);
    } finally {
      oout.close();
    } 
    return bout.toByteArray();
  }
  
  private static Object deserialize(byte[]bytes) throws Exception {
    ObjectInputStream oin = new ObjectInputStream(new ByteArrayInputStream(bytes));
    try {
      return oin.readObject();
    } finally {
      oin.close();
    }
  }
    
  public static void main(String[]args) throws Exception {    
    TestObject testObject = new TestObject(123);
    byte[]bytes = serialize(testObject);
    System.out.println("" + deserialize(bytes));
  }
}

これを実行すると、結果は

ReplacedObject 123

となる。また、上記のコメントアウトされた行は、オブジェクトの読込み時に「自身の代わりにあいつを返せ」という指定で、これを有効化すると今度は

TestObject 123

という結果になり、元に戻ることがわかる。

より現実的な例

上記のように一つのオブジェクトだけを直列化する場合というのは考えにくい。実際には参照元の他のオブジェクトを直列化するというケースが多いと思われる。 以下はエラーになる。

package test;

import java.io.*;

public class SerialTest {

  public static class TestObject implements Serializable {
    int value;
    public TestObject(int value) {
      this.value = value;
    }
    @Override public String toString() {
      return "TestObject " + value;
    }
    
    /** 書き込み時、自身の代わりにReplacedObjectを直列化せよ、という指定 */
    public Object writeReplace() throws ObjectStreamException {
      return new ReplacedObject(value);
    }    
  }
  
  public static class ReplacedObject implements Serializable {
    int value;
    public ReplacedObject(int value) {
      this.value = value;
    }
    @Override public String toString() {
      return "ReplacedObject " + value;
    }
    
//    /** 読込み時、自身の代わりにTestObjectを返せ、という指定 */
//    public Object readResolve() throws ObjectStreamException {
//      return new TestObject(value);
//    }
  }
  
  public static class CompositeObject implements Serializable {
    public TestObject[]tests;
    public CompositeObject(TestObject...tests) {
      this.tests = tests;
    }
    @Override public String toString() {
      StringBuilder buf = new StringBuilder();
      buf.append("CompositeObject:");
      for (TestObject o: tests) buf.append(o.toString() + " ");
      return buf.toString();
    }
  }
  
  private static byte[]serialize(Object object) throws Exception  {
    ByteArrayOutputStream bout = new ByteArrayOutputStream();
    ObjectOutputStream oout = new ObjectOutputStream(bout);    
    try {
      oout.writeObject(object);
    } finally {
      oout.close();
    } 
    return bout.toByteArray();
  }
  
  private static Object deserialize(byte[]bytes) throws Exception {
    ObjectInputStream oin = new ObjectInputStream(new ByteArrayInputStream(bytes));
    try {
      return oin.readObject();
    } finally {
      oin.close();
    }
  }
    
  public static void main(String[]args) throws Exception {    
    CompositeObject compObject = new CompositeObject(
        new TestObject(123),
        new TestObject(456)
    );
    byte[]bytes = serialize(compObject);
    System.out.println("" + deserialize(bytes));
  }
}

これを実行すると、

Exception in thread "main" java.lang.ArrayStoreException: test.SerialTest$ReplacedObject
        at java.io.ObjectInputStream.readArray(ObjectInputStream.java:1667)
......

readResolveがコメントアウトされているので、CompositeObjectのtests配列の中身として、ReplacedObjectが作成されてしまうからである。コメントアウト部分を有効にすると、

CompositeObject:TestObject 123 TestObject 456 

となる。

何の役に立つのか?

一つ考えられるのは、「直列化するとサイズが大きくなりすぎる」ケースである。仮に大量のTestObjectCompositeObjectに保持され、そのそれぞれが大きなオブジェクトである場合直列化形式でのサイズを少しでも減らしたいと思う。そんな時に、直列化形式上でのみReplacedObjectで保持し、メモリ上ではいつでもTestObjectの形で使うといったことが可能になる。

もう一つは、直列化復帰するオブジェクトを変更したい場合である。以下に例を示す。

直列化復帰するオブジェクトを変更する例

以下では、直列化するオブジェクトTestObjectであるが、復帰されるのは常にReplacedObjectになる。これにより、以前に作成した直列化ストリームの中にあるすべてのTestObjectを自動的にReplacedObjectにすりかえることができる。 ReplacedObjectは一切直列化されないので、Serializableを実装していなくてもよいことに注意。 ※無論、CompositeObjectはこのままでは直列化できなくなる。

package test;

import java.io.*;

public class SerialTest {

  public static class TestObject implements Serializable {
    int value;
    public TestObject(int value) {
      this.value = value;
    }
    @Override public String toString() {
      return "TestObject " + value;
    }  
    /** 読込み時、自身の代わりにTestObjectを返せ、という指定 */
    public Object readResolve() throws ObjectStreamException {
      return new ReplacedObject(value);
    }
  }

  public static class ReplacedObject {
    int value;
    public ReplacedObject(int value) {
      this.value = value;
    }
    @Override public String toString() {
      return "ReplacedObject " + value;
    }
  }
  
  public static class CompositeObject implements Serializable {
    public Object[]tests;
    public CompositeObject(Object...tests) {
      this.tests = tests;
    }
    @Override public String toString() {
      StringBuilder buf = new StringBuilder();
      buf.append("CompositeObject:");
      for (Object o: tests) buf.append(o.toString() + " ");
      return buf.toString();
    }
  }

  public static void main(String[]args) throws Exception {    
    CompositeObject compObject = new CompositeObject(
        new TestObject(123),
        new TestObject(456)
    );
    byte[]bytes = serialize(compObject);
    System.out.println("" + deserialize(bytes));
  }

  // serialize/deserialize省略
}