6
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

PLISEAdvent Calendar 2021

Day 1

[Java] 匿名クラスとインスタンスイニシャライザを使って初期化したデータのシリアライズに失敗した話

Last updated at Posted at 2021-11-30

以前セッション管理の方法をメモリからDBに変更した際に、シリアライズ周りでエラーが起きたのでそれの原因と対策についてまとめる。

前提

Javaではデータの初期化に下記のような方法がある。

List<String> animals = new ArrayList<>() {
	{
		add("いぬ");
		add("ねこ");
		add("たぬき");
	}
};

これは匿名クラスとインスタンスイニシャライザを使って初期化を行うという方法である。
通常Listの初期化にはList.of(), Arrays.asList()などを使うが、これはimmutableなListを作成するため、後から要素の追加などが出来ない。
しかし、匿名クラスとインスタンスイニシャライザを使えばArrayListのサブクラスとしてインスタンスを作成するため、その後の要素の追加が可能になる。

起きたこと

Serializableを実装したSchoolオブジェクトをシリアライズしようとしたエラーが発生した。

School.java
// シリアライズ可能なクラス
public class School implements Serializable {

    private List<String> studentNames;

    public List<String> getStudentNames() {
        return studentNames;
    }

    public void setStudentNames(final List<String> studentNames) {
        this.studentNames = studentNames;
    }
}
Service.java
public class Service {
    public void process() {
        // 匿名クラス + インスタンスイニシャライザを使ってリストを初期化
        final List<String> studentNames = new ArrayList<>() {
            {
                add("ひろし");
                add("みか");
                add("たくろう");
                add("たけし");
            }
        };
        final School school = new School();
        school.setStudentNames(studentNames);

        try (FileOutputStream fos = new FileOutputStream("school.obj");
                ObjectOutputStream oos = new ObjectOutputStream(fos)) {
            // Schoolのシリアライズ
            oos.writeObject(school);
        } catch (final IOException e) {
            e.printStackTrace();
        }
    }
}
実行クラス(Main.java)
public class Main {
    public static void main(final String[] args) {
        final Service service = new Service();
        service.process();
    }
}
エラー内容
java.io.NotSerializableException: Service
	at java.base/java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1185)
	at java.base/java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1553)
	at java.base/java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1510)
	at java.base/java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1433)
	at java.base/java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1179)
	at java.base/java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1553)
	at java.base/java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1510)
	at java.base/java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1433)
	at java.base/java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1179)
	at java.base/java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:349)
	at Service.process(Service.java:22)
	at Main.main(Main.java:4)

原因

NotSerializableExceptionはシリアライズしようとしたオブジェクトがSerializableを実装(implements)していないときに投げられる例外で、今回の場合だとServiceクラスがSerializableを実装していないためエラーとなった。
そもそもシリアライズしようとしているオブジェクトはSchoolなのになぜServiceが出てくるのか?
これが今回のポイントで、インスタンスメソッド内で匿名クラスを使うとその匿名クラスはコンパイル時に親クラスの情報を持つようになる。

実際にコンパイル後のクラスの情報を見てみる。

$ cd bin
$ ls
 Main.class   School.class  'Service$1.class'   Service.class

コンパイルすると匿名クラスも一つのclassファイルとして生成されていて、今回の場合だとService$1.classがそれにあたる。

javapコマンドで逆アセンブルすることでclassファイルのフィールドやメソッドを確認することができる。

$ javap -p 'Service$1.class'
Compiled from "Service.java"
class Service$1 extends java.util.ArrayList<java.lang.String> {
  final Service this$0;
  Service$1(Service);
}

すると匿名クラスのService$1.classはフィールドにServiceを持っていることがわかる。

これでSchoolオブジェクトをシリアライズしようとしたときにServiceオブジェクトが登場することがわかった。

対策

匿名クラスを使ったデータの初期化は行わない

今回の例でもただインスタンスの作成とデータの初期化を同時やりたかっただけなので、普通にaddaddAllなどをすればよいし、List.of(), Arrays.asList()を引数に渡してインスタンス生成するやり方でも良い。

final List<String> studentNames = new ArrayList<>();
studentNames.add("ひろし");
studentNames.add("みか");
studentNames.add("たくろう");
studentNames.add("たけし");
final List<String> studentNames = new ArrayList<>(List.of("ひろし", "みか", "たくろう", "たけし"));

自分の書いているコードの意味をしっかりと理解する

ArrayListの初期化の方法を検索すると、今回の匿名クラスとインスタンスイニシャライザを使って初期化する方法を紹介している記事は多数見つかる。
そのコードを理解しないままに使ってしまうと、思わぬところでエラーとなってしまうため、この構文が何をしているものなのか、それを使った上で問題はないのかをしっかり理解した上で使うようにする。

6
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?