e49d20b6-min

クラスのフィールドに指定する『serialVersionUID』。

Eclipseで警告出るから指定するけど、「これってなんなのめんどい」という方も多いはず。

「serialVersionUIDとは何なのか?」を具体的なサンプルで説明したいと思います。

serialVersionUIDは、どんな時に指定する必要があるか?

インターフェースjava.io.Serializableを実装した場合に必要。

親クラスが実装していた場合にも必要になる。

じゃぁSerializableってなんなのよ?

まずはSerializableをimplementsしたクラスを作成

名前と年齢を設定する簡単なクラスを作りましょう。

import java.io.Serializable;


public class Human implements Serializable{

  private static final long serialVersionUID = 1L;
  
  private final String name;
  
  private final int age;
 
  public Human(String name,int age) {
    this.name = name;
    this.age = age;
  }

  public String getName() {
    return name;
  }

  public int getAge() {
    return age;
  }
}

Serializable実装クラスはオブジェクト情報をファイルに書き込める。

オブジェクト情報をまるまるファイルに書き込んでバイナリデータを作れます。

import java.io.FileOutputStream;
import java.io.ObjectOutputStream;

public class SerializableWrite {
  
  public static void main(String[] args) throws Exception{
    
    Human human = new Human("John",15);
    
    //オブジェクトをファイルに書き込む
    try(ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("human.obj"))){
    
      oos.writeObject(human);
    
    }
  }
}

逆にバイナリデータからインスタンスを復元できる。

次は、ファイルを読み込んでインスタンスを作ってみます。

import java.io.FileInputStream;
import java.io.ObjectInputStream;


public class MainSerializeRead {

  public static void main(String[] args) throws Exception{
    
    //ファイルからオブジェクト情報を読み込む
    try(ObjectInputStream oos = new ObjectInputStream(new FileInputStream("human.obj"))){
    
      Human human = (Human)oos.readObject();
    
      System.out.println(human.getName() + ":" + human.getAge());

    }
  }
}
実行結果
John:15

このようにSerializableなクラスは、「バイト配列化」が可能になる。

「バイト配列化」が可能ということは、Stream経由でインスタンス情報をハードディスクやネットワークに送受信できたりする。

EJBの分散オブジェクトもこの機能で実現してるんですよ。

荒業だがデータベースのBLOB型にINSERTして(java.sql.PreparedStatement.setObjectメソッド)、SELECTで取り出してObjectを復元させるということもできる。(ResultSet.getObjectメソッド)。

serialVersionUIDを変更するとどうなるのか?

serialVersionUIDを変更して読み込もうとすると例外が発生する。

private static final long serialVersionUID = 2L;
実行結果
Exception in thread "main" java.io.InvalidClassException: Human; local class incompatible: stream classdesc serialVersionUID = 1, local class serialVersionUID = 2
	at java.io.ObjectStreamClass.initNonProxy(Unknown Source)
	at java.io.ObjectInputStream.readNonProxyDesc(Unknown Source)
	at java.io.ObjectInputStream.readClassDesc(Unknown Source)
	at java.io.ObjectInputStream.readOrdinaryObject(Unknown Source)
	at java.io.ObjectInputStream.readObject0(Unknown Source)
	at java.io.ObjectInputStream.readObject(Unknown Source)
	at MainSerializeRead.main(MainSerializeRead.java:12)

serialVersionUIDは、シリアライズなクラスのバージョンを管理するためにあるんですね!

シリアライズとか必要ないんだけど警告どうすんの?

serialVersionUID書いてると「シリアライズなクラスです!テストもしっかりやってます!」という印象を受けてしまうかも。

必要ないならserialVersionUIDを書かずに"@SuppressWarnings"で対応した方が親切。

import java.io.Serializable;


@SuppressWarnings("serial")
public class Test implements Serializable{

}

Serializableなクラスを作る上での注意点

フィールドに『シリアライズできないクラス』があると、 シリアライズ時に例外が発生する。

import java.io.Serializable;
import javax.tools.DiagnosticCollector;

public class Human implements Serializable{

  private static final long serialVersionUID = 1L;
  
  private final String name;
  
  private final int age;
  
  //Serializableをimplementsしていないクラス
  private final DiagnosticCollector<String> diagnosticCollector = new DiagnosticCollector<>();

 
  public Human(String name,int age) {
    this.name = name;
    this.age = age;
  }

  public String getName() {
    return name;
  }

  public int getAge() {
    return age;
  }
}
実行結果
Exception in thread "main" java.io.NotSerializableException: javax.tools.DiagnosticCollector
	at java.io.ObjectOutputStream.writeObject0(Unknown Source)
	at java.io.ObjectOutputStream.defaultWriteFields(Unknown Source)
	at java.io.ObjectOutputStream.writeSerialData(Unknown Source)
	at java.io.ObjectOutputStream.writeOrdinaryObject(Unknown Source)
	at java.io.ObjectOutputStream.writeObject0(Unknown Source)
	at java.io.ObjectOutputStream.writeObject(Unknown Source)
	at SerializableWrite.main(SerializableWrite.java:13)

transientアノテーションで無視されるようにしておこう

上記のような例外が発生しないように『transient』で無視されるように設定しなければならない。

import java.io.Serializable;
import javax.tools.DiagnosticCollector;


public class Human implements Serializable{

  private static final long serialVersionUID = 2L;
  
  private final String name;
  
  private final int age;
  
  //transientでシリアライズ時に無視されるフィールド
  private transient final DiagnosticCollector<String> diagnosticCollector = new DiagnosticCollector<>();

 
  public Human(String name,int age) {
    this.name = name;
    this.age = age;
  }

  public String getName() {
    return name;
  }

  public int getAge() {
    return age;
  }
}