Java における直列化と型 [Java]
このところめっきりプログラミングをしていないのだけど、最近は SJC-P の教科書を買って Java を勉強していて、直列化周りの言語仕様に興味と疑問が湧いた。
Java ではクラスが Serializable インターフェイスを implements すると ObjectOutputStream とかを使ってシリアライズできるようになる。この Serializable インターフェイスは実装すべきメソッドというものがない空のインターフェイスであって、これはマーカーインターフェイスと呼ばれる。
まずこのマーカーインターフェイスというのがちょっと興味深い。これは単にこの名前のインターフェイスを implements しているということだけに意味があるものだ。例えば nominal な型付けというものが全くなくて structural な型付けしか存在しないような静的なオブジェクト指向言語を考えてみると、そういう言語ではマーカーインターフェイスの居場所が無い。だからマーカーインターフェイスの用途すべてが他の仕組みに還元できるわけではないということが示せれば nominal な型付けを完全放棄してはいけない根拠の一つになるかもしれない。
次に疑問点として、何故 ObjectOutputStream#writeObject の引数は Serializable obj ではなくて Object obj なのだろうか、ということを思った。
writeObject は Serializable でないオブジェクトを受け取ると実行時エラーになる、つまりマーカーインターフェイスのマーカーは実行時に利用されているわけだけど、引数を Serializable にすると現在手に入るコンパイラの仕組みでこれをコンパイルエラーにすることができる。もっというと Serializable を implements したクラスのすべてのフィールドが直列化可能な型であるということも、やろうと思えばコンパイル時チェックできるのではないだろうか。
後半はともかく前半に対する反論を考えてみると、たまたま Object 型(や Serializable を implements していないクラス)の変数に入っているオブジェクトが実際には直列化可能だとわかっている場合(サブクラスで Serializable を implements していて、そのクラスのインスタンスである)を救えないことになってしまう。
…と思ったのだけど、そう状況ではしょうがないのでキャストすればいいのではないだろうか。一般に Serializable にキャストできないのかとも思ったけどそうでもないようだ(変換元が final でなければ可能)。その場合 NotSerializableException が ClassCastException になるだけの違いで、「キャストを使わない限りはコンパイル時に検出可能」という線までは前進できる。
それともそこまで前進しても現実的な使用では意味がないのだろうか。ひょっとしたら何か根本的な思い違いをしているような気もするけど釈然としない。
静的に解決してしまうと、直列化可能なオブジェクトで、フィールドに、List list = new ArrayList()みたいなことができなくなってしまいます(ListはSerializableじゃないので)。更に1.4までは、要素型も総称Object型なんで、そもそもコレクションが保持できなくなってしまいます。そのあたりが、マーカインターフェースにした理由なんじゃないでしょうか。
by るいも (2008-09-27 22:57)
コメントありがとうございます。
なるほど。前半は言い方を変えると「あるクラスのサブクラスであり、かつ Serializable を implements しているようなクラス」という指定(上界が複数ある)を変数の型にできないから、ということですね。Scala だと val l: List with Serializable = new ArrayList() とかできるようなのですが。
Java の範囲でやるとしたら List のサブクラスに SerializableList を作って ArrayList をそのサブクラスにして…と考えられるけどそれはやりたくない感じがしますね。(SerializableXXX が沢山作られてしまいそう)
by ether (2008-09-28 09:22)