EnumクラスはSerializableなので、enumでオブジェクトをシリアル化しても問題はありません。もう1つのケースは、クラスにJava.util.Optionalクラスのフィールドがある場合です。この場合、次の例外がスローされます。Java.io.NotSerializableException:Java.util.Optional
そのようなクラスを処理する方法、それらをシリアル化する方法は?そのようなオブジェクトをリモートEJBに送信したり、RMIを介して送信したりすることはできますか?
これは例です:
import Java.io.ByteArrayOutputStream;
import Java.io.IOException;
import Java.io.ObjectOutputStream;
import Java.io.Serializable;
import Java.util.Optional;
import org.junit.Test;
public class SerializationTest {
static class My implements Serializable {
private static final long serialVersionUID = 1L;
Optional<Integer> value = Optional.empty();
public void setValue(Integer i) {
this.i = Optional.of(i);
}
public Optional<Integer> getValue() {
return value;
}
}
//Java.io.NotSerializableException is thrown
@Test
public void serialize() {
My my = new My();
byte[] bytes = toBytes(my);
}
public static <T extends Serializable> byte[] toBytes(T reportInfo) {
try (ByteArrayOutputStream bstream = new ByteArrayOutputStream()) {
try (ObjectOutputStream ostream = new ObjectOutputStream(bstream)) {
ostream.writeObject(reportInfo);
}
return bstream.toByteArray();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
この回答は、タイトルにある「オプションをシリアル化可能にするべきではない」という質問に対する回答です。簡単な答えは、Java Lambda(JSR-335)専門家グループ 検討および拒否 。そのメモ、および this onethis one は、戻り値が存在しない可能性がある場合に、Optional
の主要な設計目標を関数の戻り値として使用することを示します。意図は、呼び出し元がすぐにOptional
そして、存在する場合は実際の値を抽出します。値が存在しない場合、呼び出し元はデフォルト値を置き換えるか、例外をスローするか、他のポリシーを適用できます。 Optional
値を返すストリームパイプライン(または他のメソッド)。
Optional
を オプションのメソッド引数 や オブジェクトのフィールドとして保存 などの他の方法で使用することは意図されていませんでした。さらに、Optional
をシリアライズ可能にすると、永続的に保存したり、ネットワーク経由で送信したりできるようになり、どちらも元の設計目標をはるかに超える使用を促進します。
通常、フィールドにOptional
を保存するよりも、データを整理する方法が優れています。ゲッター(質問のgetValue
メソッドなど)がフィールドから実際のOptional
を返す場合、すべての呼び出し元に空の値を処理するためのポリシーを実装するように強制します。これにより、発信者間で一貫性のない動作が発生する可能性があります。多くの場合、そのフィールドが設定されているときに、そのフィールドに何らかのポリシーを適用するコードセットを指定する方が適切です。
Optional
をList<Optional<X>>
やMap<Key,Optional<Value>>
などのコレクションに入れたい場合があります。これも通常は悪い考えです。多くの場合、これらのOptional
の使用法を Null-Object 値(実際のnull
参照ではない)に置き換えるか、単にコレクションからこれらのエントリを完全に省略した方が良いでしょう。
多くのSerialization
関連の問題は、操作する実際のランタイム実装から永続的なシリアル化された形式を分離することで解決できます。
/** The class you work with in your runtime */
public class My implements Serializable {
private static final long serialVersionUID = 1L;
Optional<Integer> value = Optional.empty();
public void setValue(Integer i) {
this.value = Optional.ofNullable(i);
}
public Optional<Integer> getValue() {
return value;
}
private Object writeReplace() throws ObjectStreamException
{
return new MySerialized(this);
}
}
/** The persistent representation which exists in bytestreams only */
final class MySerialized implements Serializable {
private final Integer value;
MySerialized(My my) {
value=my.getValue().orElse(null);
}
private Object readResolve() throws ObjectStreamException {
My my=new My();
my.setValue(value);
return my;
}
}
クラスOptional
はbehaviorを実装します。これにより、(null
の使用と比較して)存在しない可能性のある値を処理するときに適切なコードを記述できます。しかし、それはデータの永続的な表現に利点を追加しません。シリアル化されたデータが大きくなるだけです…
上記のスケッチは複雑に見えるかもしれませんが、それは1つのプロパティのみでパターンを示しているためです。クラスのプロパティが多いほど、そのシンプルさが明らかになるはずです。
忘れてはならないのは、永続的なフォームを適合させる必要なく、My
の実装を完全に変更できることです…
シリアライズ可能なオプションが必要な場合は、代わりに、シリアライズ可能な guavaのオプション の使用を検討してください。
それは奇妙な省略です。
フィールドをtransient
としてマークし、writeObject()
結果自体を記述した独自のカスタムget()
メソッド、およびreadObject()
メソッドを提供する必要があります。ストリームから結果を読み取ってOptional
を復元しました。それぞれdefaultWriteObject()
とdefaultReadObject()
を呼び出すことを忘れないでください。
Vavr.ioライブラリ(以前のJavaslang)には、シリアル化可能なOption
クラスもあります。
public interface Option<T> extends Value<T>, Serializable { ... }
より一貫性のある型リストを維持し、nullの使用を避けたい場合は、1つの変な選択肢があります。
タイプの共通部分を使用して値を保存する 。ラムダと組み合わせると、次のようなことが可能になります。
private final Supplier<Optional<Integer>> suppValue;
....
List<Integer> temp = value
.map(v -> v.map(Arrays::asList).orElseGet(ArrayList::new))
.orElse(null);
this.suppValue = (Supplier<Optional<Integer>> & Serializable)() -> temp==null ? Optional.empty() : temp.stream().findFirst();
temp
変数を分離することで、value
メンバーの所有者をクローズすることを避け、シリアル化が多すぎることを防ぎます。