web-dev-qa-db-ja.com

Java.util.OptionalがSerializableではない理由、そのようなフィールドでオブジェクトをシリアル化する方法

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);
        }
    }
}
98
vanarchi

この回答は、タイトルにある「オプションをシリアル化可能にするべきではない」という質問に対する回答です。簡単な答えは、Java Lambda(JSR-335)専門家グループ 検討および拒否 。そのメモ、および this onethis one は、戻り値が存在しない可能性がある場合に、Optionalの主要な設計目標を関数の戻り値として使用することを示します。意図は、呼び出し元がすぐにOptionalそして、存在する場合は実際の値を抽出します。値が存在しない場合、呼び出し元はデフォルト値を置き換えるか、例外をスローするか、他のポリシーを適用できます。 Optional値を返すストリームパイプライン(または他のメソッド)。

Optionalオプションのメソッド引数オブジェクトのフィールドとして保存 などの他の方法で使用することは意図されていませんでした。さらに、Optionalをシリアライズ可能にすると、永続的に保存したり、ネットワーク経由で送信したりできるようになり、どちらも元の設計目標をはるかに超える使用を促進します。

通常、フィールドにOptionalを保存するよりも、データを整理する方法が優れています。ゲッター(質問のgetValueメソッドなど)がフィールドから実際のOptionalを返す場合、すべての呼び出し元に空の値を処理するためのポリシーを実装するように強制します。これにより、発信者間で一貫性のない動作が発生する可能性があります。多くの場合、そのフィールドが設定されているときに、そのフィールドに何らかのポリシーを適用するコードセットを指定する方が適切です。

OptionalList<Optional<X>>Map<Key,Optional<Value>>などのコレクションに入れたい場合があります。これも通常は悪い考えです。多くの場合、これらのOptionalの使用法を Null-Object 値(実際のnull参照ではない)に置き換えるか、単にコレクションからこれらのエントリを完全に省略した方が良いでしょう。

149
Stuart Marks

多くの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;
    }
}

クラスOptionalbehaviorを実装します。これにより、(nullの使用と比較して)存在しない可能性のある値を処理するときに適切なコードを記述できます。しかし、それはデータの永続的な表現に利点を追加しません。シリアル化されたデータが大きくなるだけです…

上記のスケッチは複雑に見えるかもしれませんが、それは1つのプロパティのみでパターンを示しているためです。クラスのプロパティが多いほど、そのシンプルさが明らかになるはずです。

忘れてはならないのは、永続的なフォームを適合させる必要なく、Myの実装を完全に変更できることです…

13
Holger

シリアライズ可能なオプションが必要な場合は、代わりに、シリアライズ可能な guavaのオプション の使用を検討してください。

9
Eric Hartford

それは奇妙な省略です。

フィールドをtransientとしてマークし、writeObject()結果自体を記述した独自のカスタムget()メソッド、およびreadObject()メソッドを提供する必要があります。ストリームから結果を読み取ってOptionalを復元しました。それぞれdefaultWriteObject()defaultReadObject()を呼び出すことを忘れないでください。

4
user207421

Vavr.ioライブラリ(以前のJavaslang)には、シリアル化可能なOptionクラスもあります。

public interface Option<T> extends Value<T>, Serializable { ... }
1
Przemek Nowak

より一貫性のある型リストを維持し、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メンバーの所有者をクローズすることを避け、シリアル化が多すぎることを防ぎます。

0
Dan Gravell