- PR -

ObjectにSerializableが実装されていない理由

投稿者投稿内容
Guri
会議室デビュー日: 2007/01/16
投稿数: 12
投稿日時: 2007-02-28 19:44
以前から気になっていたのですが、
個人的には、java.lang.Objectにjava.io.Serializableが実装されていても
良いのにと思っています。

仮にjava.lang.ObjectにSerializableが実装されていた場合に、
起こる不具合や、その可能性、またはSerializableが実装されていない
考えられる理由について、教えて下さい。

宜しくお願いします。

[ メッセージ編集済み 編集者: Guri 編集日時 2007-02-28 19:49 ]
あしゅ
ぬし
会議室デビュー日: 2005/08/05
投稿数: 613
投稿日時: 2007-02-28 20:59
引用:

Guriさんの書き込み (2007-02-28 19:44) より:
仮にjava.lang.ObjectにSerializableが実装されていた場合に、
起こる不具合や、その可能性、またはSerializableが実装されていない
考えられる理由について、教えて下さい。



ソケットやファイルハンドルなどがSerializableだったとして嬉しいですか?
これらは他のプロセスで逆シリアル化してもおそらく何の意味もありません。

ObjectをSerializableにするということは、
全てのクラスに対してシリアル化可能な事を強制する意味になります。
#設計的にはNotSerializableインタフェースを導入する手もありますけど。

他にもセキュリティ的によろしくない場合もあります。
外部に持ち出させたくないデータでも一度シリアル表現を経由することで
JavaのSecurityManagerを回避して送信・改竄できるようになってしまいます。
かつのり
ぬし
会議室デビュー日: 2004/03/18
投稿数: 2015
お住まい・勤務地: 札幌
投稿日時: 2007-02-28 22:00
全てのクラスがSerializableを実装していると、
いろんなフィールドでtransientをつけないといけませんね。

あしゅさんの仰るとおり、外部リソース等の
プレースホルダとなるようなクラスでは、全く意味がありません。

直列化とは、直列化するだけではなく、
別環境で同じ状態に復元できることも重要です。
例えばリモートのVMへのインスタンスの転送などです。
Guri
会議室デビュー日: 2007/01/16
投稿数: 12
投稿日時: 2007-03-01 15:44
あしゅさん
かつのりさん

返答ありがとうございます。

引用:

あしゅさんの書き込み (2007-02-28 20:59) より:

ソケットやファイルハンドルなどがSerializableだったとして嬉しいですか?
これらは他のプロセスで逆シリアル化してもおそらく何の意味もありません。

ObjectをSerializableにするということは、
全てのクラスに対してシリアル化可能な事を強制する意味になります。
#設計的にはNotSerializableインタフェースを導入する手もありますけど。

他にもセキュリティ的によろしくない場合もあります。
外部に持ち出させたくないデータでも一度シリアル表現を経由することで
JavaのSecurityManagerを回避して送信・改竄できるようになってしまいます。



引用:

かつのりさんの書き込み (2007-02-28 22:00) より:
全てのクラスがSerializableを実装していると、
いろんなフィールドでtransientをつけないといけませんね。

あしゅさんの仰るとおり、外部リソース等の
プレースホルダとなるようなクラスでは、全く意味がありません。

直列化とは、直列化するだけではなく、
別環境で同じ状態に復元できることも重要です。
例えばリモートのVMへのインスタンスの転送などです。


セキュリティについては納得しました。
今、ぱっとは思いつきませんが、直列化可能なままで、SecurityManagerを通す実装自体もできそうな気もしますが。

ファイルハンドル等の外部リソースや、ソケットのように直列化しても無意味なものがあることや、
別環境で同じ状態に復元できなければ、無意味なことは承知しています。

ただ、その直列化しても無意味なクラスと直列化可能クラスの数や、Serializableを実装するためのコストを考えると、
どちらが合理的かとも思うのです。

下記の別スレッドでも書いたのですが、
http://www.atmarkit.co.jp/bbs/phpBB/viewtopic.php?topic=37029&forum=12
例えば、java.awt.AlphaComposite(以下AlphaCompositeと略)は、Serializableを実装していないうえに、
finalクラスであるために、そもそもサブクラスを作って、Serializableを実装する選択肢自体ありません。

また、java.awt.Paint(以下Paintと略)インタフェイスを実装しているjava.awt.ColorはSerializableであるのに対して、
同じPaintインタフェイスを実装しているjava.awt.GradientPaint(以下GradientPaintと略)はSerializableを実装していません。

このため、GradientPaintをSerializableにするためには、まずGradientPaintのサブクラスを作成して、Serializableを実装します。
つぎに、serialVersionUIDを定義します。
続いて、privateのwriteObjectを実装します。

ただ、ここで問題があって、GradientPaintのtransientでないprivateメンバ変数として、
Serializableが実装されていないjava.awt.geom.Point2D(以下Point2Dと略)があるため、
Point2Dの情報を保存するするためには、サブクラスで新たに直列化可能な変数を定義したり、
Point2D情報を直列化可能な変数に置換したり、直列化可能な何らかの仕掛けを用意しないといけません。
そもそもPoint2Dが直列化可能であれば、上記の処理は不要なはずです。

もしかしたら、上記のような処置を施さず、直列化可能なGradientPaintを、自分で一から作成するかもしれません。

また、直列化不能なGradientPaintでなく、直列化可能なGradientPaintを使用するように
ファクトリメソッドを用意する必要があるかもしれません。
そして、実装者が、このファクトリメソッドを使用するように強制する、規約や機構が必要になるかもしれません。
新たなテストも必要になるでしょう。

いずれにしても、java.lang.Objectが直列化可能でない(Serializableを実装していない)という基本方針ですと、弊害として、
既存のクラスを直列化可能にするだけで、コストが非常に掛かりますし、
実行時に直列化できないというバグが入り込む余地は高まりそうです。

少々話しを大袈裟にしましたが、既存のクラスを直列化可能にするためだけに、
上記のような処置を施さなければいけないのは、どうなのかと思うのです。

一例として、個人的によく使うクラスのAWTを使用しましたが、よく使用するクラスは人それぞれなので、
無数のクラスのなかで同様の問題があるだろうと推測しています。

上記の例のAlphaComposite,GradientPaintやPoint2D(もっと言えば、java.awt.geomパッケージ)は、
Serializableの実装し忘れだと個人的には考えていますが。

アクセス制御のAllowAll,DenyAllではないですが、SerializableAll,UnserializableAllかという方針にもよりますが、
ただ、Serializableを実装し忘れるだけで、こんな無駄なコストが掛かるのであれば、
SerializableAllがベースになって欲しいところです。

Serializableがどのクラスにも実装可能で、ただのマーカーインタフェイスならば、
そもそもSerializable自体を採用せず、java.lang.ObjectサブクラスでSerializableを制御するように
しなかったのは何故なのかと疑問を持ちます。

例えば、下記コードのように。
コード:

public class java.lang.Object{
//あしゅさんの仰るSecurityManagerを考慮して
//このほかに不正にオーバーライドできない何らかのフラグが必要でしょう。
/**
* 直列化可能かどうかは、このメソッドの戻り値で判断します。
* 環境に依存する外部リソース等、直列化したくない、直列化不要な場合は
* サブクラスでオーバーライドしてfalseを返すように実装します。
* 例えば、ファイルハンドラの基底クラスでfalseを返すように実装します。
*/
public boolean isSerializable(){
//基本はSerializableAll
return true;
}
//略
}



このコードとおりでなく、サブクラスでSerializableを制御する案でなくてよいのですが、
java.lang.Objectが直列化できるわけではなくした設計者の実際の意図は分からずとも、
なにか、より明確な意図らしいものを得たいのです。。
漠然としていて申し訳ないです。

引用:

全てのクラスがSerializableを実装していると、
いろんなフィールドでtransientをつけないといけませんね。


これは、以前考えたのですが、transientを反転して考えれば良いだけだと思うので、
取り立てて問題にならないのではと考えています。

引用:

ソケットやファイルハンドルなどがSerializableだったとして嬉しいですか?


冗談ですが、嬉しいか嬉しくないかという感情の観点からいうと、
嬉しくも悲しくも、特になんの感情も持ち合わせません。


[ メッセージ編集済み 編集者: Guri 編集日時 2007-03-01 15:54 ]
あしゅ
ぬし
会議室デビュー日: 2005/08/05
投稿数: 613
投稿日時: 2007-03-01 16:48
引用:

Guriさんの書き込み (2007-03-01 15:44) より:
セキュリティについては納得しました。
今、ぱっとは思いつきませんが、直列化可能なままで、SecurityManagerを通す実装自体もできそうな気もしますが。



全てのオブジェクトが基本的に外部へ持ち出し可能だとすると、
一般の開発者が必ず意識しなくてはならない事が増えるわけです。

これは開発者がシリアル化機構の存在を知らなかった場合でもです。
こうなると、潜在的なセキュリティホールは増えると思いませんか?

まぁ、SecurityManagerはJVMのデフォルトでは無効ですし、
その状態ではリフレクション経由でprivateフィールドの書き換えすら
許可されているので、そういう理由もありえる、とだけ捉えて下さい。

引用:

ただ、その直列化しても無意味なクラスと直列化可能クラスの数や、Serializableを実装するためのコストを考えると、
どちらが合理的かとも思うのです。



合理化だけが理由ではJavaの言語仕様の方針に反すると思います。

また、シリアル化はVMプロセスと外界とのインタフェースでもあるので、
プログラムの分野によってはそう簡単には変更できないものです。

それを開発者が意図する(Serializableを実装する)以前から
プログラムの外界に知らせる必要のない内部構造すら保ったまま
強制的に決められてしまう仕様は好ましいものではないと思います。

引用:

Serializableがどのクラスにも実装可能で、ただのマーカーインタフェイスならば、
そもそもSerializable自体を採用せず、java.lang.ObjectサブクラスでSerializableを制御するように
しなかったのは何故なのかと疑問を持ちます。



isSerializable()を導入したとすると、
動的にシリアル化可能であるか不可能であるか変更できますし、
クラス単位ではなくインスタンス単位で判断する必要もあります。

今設計し直すなら@Serializableアノテーションならアリでしょう。
クラスのメタデータの一種(シリアル化可能属性)とも捉えられるので。

引用:

一例として、個人的によく使うクラスのAWTを使用しましたが、よく使用するクラスは人それぞれなので、
無数のクラスのなかで同様の問題があるだろうと推測しています。



AWTの場合はほんとにバグっぽいですね。JDK 6からはSerializableなようです。
http://download.java.net/jdk/jdk-api-localizations/jdk-api-ja/builds/latest/html/ja/api/java/awt/geom/package-tree.html
nagise
ぬし
会議室デビュー日: 2006/05/19
投稿数: 1141
投稿日時: 2007-03-01 17:09
そういえば、Map.Entryの実装クラスがSerializableになっていなくて
はまったことがありましたねぇ。TreeMapのときだったか。(JDK1.5)

個人的には宣言もしないのにデフォルトでオープンになっているというのは気味が悪いですね。
やはり、直列化するには考慮した上で直列化可能なクラスとしておけ、
という意味合いで(マーカーインターフェース方式がいいかはさておき)
明示的に直列化可能を宣言する方法論でいいように思います。

そういえば、classがデフォルトでオーバーライド可能なのも、
実はあまりよくないなぁと思うのですね。
オーバーライドされることを前提として設計されていないクラスを
オーバーライドしてうまく動くのかよ、と。

ただ、デフォルトでfinalにされていると、実務上、継承クラスを作って
プロキシ的に動かしてどうにかする、などのハックができなくなるので
困ることもあるのかなぁ、と思います。
悩ましいところですね。
sawat
大ベテラン
会議室デビュー日: 2006/08/02
投稿数: 112
投稿日時: 2007-03-01 17:47
個別のクラスにおいて、直列化可能であるべきクラスが Serializable を実装していないというのは問題ですが、だからといって Object が Serializable を実装するのは単純に間違いだと思います。
理由はすでに色々あがっていますが、Serializable を安易に実装するとクラスのカプセル化が破られてしまうというのもあります。

また、Serializable でないフィールドを持ったクラスを直列化したい場合は、フィールドのオブジェクトを無理やり Serializable なものに置き換えるよりも、親のオブジェクトの方に writeObject と readObject を実装して対処するほうが楽なケースも多いと思います。

# この辺の話も、やっぱり Effective Java に・・・。
# ちなみに Effective Java はもうじき第2版がでます。
# 邦訳も今年中には出そうです*
# なので今すぐ旧版を買って、第2版(原書、あるいは邦訳)が出しだいそれを買うのがよいですね
Guri
会議室デビュー日: 2007/01/16
投稿数: 12
投稿日時: 2007-03-01 19:05
皆さん 御回答ありがとうございます。

> あしゅさん

引用:

全てのオブジェクトが基本的に外部へ持ち出し可能だとすると、
一般の開発者が必ず意識しなくてはならない事が増えるわけです。

これは開発者がシリアル化機構の存在を知らなかった場合でもです。
こうなると、潜在的なセキュリティホールは増えると思いませんか?


確かにそうですね。

引用:

合理化だけが理由ではJavaの言語仕様の方針に反すると思います。


申し訳ないです。
これは、ちょっと理解できません。

引用:

それを開発者が意図する(Serializableを実装する)以前から
プログラムの外界に知らせる必要のない内部構造すら保ったまま
強制的に決められてしまう仕様は好ましいものではないと思います。

isSerializable()を導入したとすると、
動的にシリアル化可能であるか不可能であるか変更できますし、
クラス単位ではなくインスタンス単位で判断する必要もあります。


確かにそうですね。

引用:

今設計し直すなら@Serializableアノテーションならアリでしょう。
クラスのメタデータの一種(シリアル化可能属性)とも捉えられるので。


賛成です。そうなってくれると良いです。

引用:

AWTの場合はほんとにバグっぽいですね。JDK 6からはSerializableなようです。
http://download.java.net/jdk/jdk-api-localizations/jdk-api-ja/builds/latest/html/ja/api/java/awt/geom/package-tree.html



情報ありがとうございます。JDK6は未読でした。
確かにjava.awt.geom.*はJDK 6からはSerializableなようですね。
でも、相変わらずAlphaComposite,GradientPaintは対象外ですね。

>nagise さん

引用:

そういえば、Map.Entryの実装クラスがSerializableになっていなくて
はまったことがありましたねぇ。TreeMapのときだったか。(JDK1.5)


こういうのに嵌りたくないですね。
差し支えなければ、どのように対処されたか教えて頂けませんか?

引用:

ただ、デフォルトでfinalにされていると、実務上、継承クラスを作って
プロキシ的に動かしてどうにかする、などのハックができなくなるので
困ることもあるのかなぁ、と思います。
悩ましいところですね。


あしゅさんの『強制的に決められてしまう仕様』だけを引用しますが、
設計者の気持ちも分かるのですが、同様にfinalなクラスもできれば避けて欲しいですね。

引用:

個別のクラスにおいて、直列化可能であるべきクラスが Serializable を実装していないというのは問題ですが、
だからといって Object が Serializable を実装するのは単純に間違いだと思います。
理由はすでに色々あがっていますが、Serializable を安易に実装するとクラスのカプセル化が破られてしまうというのもあります。


カプセル化の概念を最初に知ったときは、納得したのですが。
条件付ですが、reflectionやjava.beans.*パッケージ経由でreflectionを使用して、
privateな属性を書き換えられるあたりは、「本当にカプセル?」と思ってしまうのは、
私だけでしょうか。。

引用:

また、Serializable でないフィールドを持ったクラスを直列化したい場合は、
フィールドのオブジェクトを無理やり Serializable なものに置き換えるよりも、
親のオブジェクトの方に writeObject と readObject を実装して対処するほうが楽なケースも多いと思います。


非常に興味があるのですが、これは自分で変更できないクラス(前述のGradientPaint)でも有効な話ですか?
と書いてきてひらめきました。ひょっとして、匿名クラスを使ってHackするんですか?
匿名クラスなんてしょっちゅう使っているのに、あぁ、なんで気づかなかったのか。阿呆でした。

引用:

# この辺の話も、やっぱり Effective Java に・・・。
# ちなみに Effective Java はもうじき第2版がでます。
# 邦訳も今年中には出そうです*。
# なので今すぐ旧版を買って、第2版(原書、あるいは邦訳)が出しだいそれを買うのがよいですね


是非読みます。

ObjectにSerializableが実装されていないと考えられる理由、考え方、よく理解できました。
よいインスピレーションも得られました。
皆様、お忙しい中、本当にありがとうございました。

スキルアップ/キャリアアップ(JOB@IT)