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


107

EnumクラスはSerializableなので、列挙型を使用してオブジェクトをシリアル化しても問題はありません。もう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);
        }
    }
}

Optionalとしてマークされている場合Serializableget()シリアル化できない何かが返された場合はどうなりますか?
WW。

10
@WW。NotSerializableException,もちろんあなたは得るでしょう。
ローン侯爵14

7
@WW。コレクションのようなものです。ほとんどのコレクションクラスはシリアル化可能ですが、コレクションに含まれるすべてのオブジェクトもシリアル化可能である場合にのみ、コレクションインスタンスを実際にシリアル化できます。
スチュアートマークス

私の個人的な見解:オブジェクトのシリアル化でさえも適切に単体テストを行うように人々に思い出させることは重要ではありません。ちょうどjava.io.NotSerializableException(java.util.Optional)に
遭遇しました

回答:


171

この答えは、タイトルの質問「シリアル化はオプションではないのか?」に対する回答です。短い答えは、Java Lambda(JSR-335)の専門家グループがそれを検討して拒否したということです。そのメモ、そしてこれこれOptionalは、戻り値がない場合の関数の戻り値としての主な設計目標が使用されることを示しています。その意図は、呼び出し側がすぐにをチェックし、Optional存在する場合は実際の値を抽出することです。値が存在しない場合、呼び出し元はデフォルト値を代入したり、例外をスローしたり、他のポリシーを適用したりできます。これは通常、Optional値を返すストリームパイプライン(または他のメソッド)の最後から流暢なメソッド呼び出しをチェーンすることによって行われます。

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

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

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


26
よくやったスチュアート。推論するなら、それは並外れた連鎖であると言わなければなりません。それは、インスタンスのメンバー型として使用することを意図していないクラスを設計するために並外れたことです。特に、Javadocのクラスコントラクトでそれが述べられていない場合。おそらく、クラスの代わりにアノテーションを設計するべきでした。
ローン侯爵14

1
これは、Sutartの回答の背後にある理論的根拠に関する優れたブログ投稿です。blog.joda.org
Wesley Hartford

2
シリアル化可能なフィールドを使用する必要がないのは良いことです。それ以外の場合は、これは災害になります
クル

48
興味深い答えです。私にとって、デザインの選択はまったく受け入れられず、見当違いでした。「通常は、オプションをフィールドに格納するよりもデータを整理する方が良い方法がある」とあなたは言いますが、おそらくそうではないかもしれませんが、それは言語ではなくデザイナーの選択であるべきです。これは、JavaでScalaのオプションが欠落しているこれらのケースのもう1つの例です(Scalaのオプションはシリアル化可能であり、モナドのガイドラインに従います)
Guillaume

37
「オプションの主な設計目標は、戻り値がない場合の関数の戻り値として使用することです。」まあ、リモートEJBの戻り値には使用できないようです。グレート...
ティロ

15

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実装します。ただし、データの永続的な表現にメリットはありません。シリアル化されたデータが大きくなるだけです…null

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

そして、忘れずにMy、永続的なフォームを適応させる必要なしに、完全に実装を変更する可能性…


2
+1、ただしフィールドが多いほど、コピーするボイラープレートが多くなります。
Marko Topolnik 14

1
@Marko Topolnik:プロパティと方向ごとに最大1行。ただし、に適切なコンストラクターを提供することで、class My通常は他の用途にも役立つためreadResolve、1行の実装になり、ボイラープレートをプロパティごとに1行に減らすことができます。Myいずれにせよ、変更可能な各プロパティがクラスに少なくとも7行のコードを含んでいるという事実はあまりありません。
Holger

私は同じトピックをカバーする投稿を書きました。これは基本的に、この回答の長いテキストバージョンです。シリアル化オプション
Nicolai

欠点は、オプションを使用するすべてのBean / pojoに対してそれを行う必要があることです。しかし、それでも素晴らしいアイデアです。
GhostCat 2017


4

それは奇妙な省略です。

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


クラスのコードを所有している場合は、単なるオブジェクトをフィールドに格納する方が便利です。そのような場合のオプションクラスは、クラスのインターフェースに対して制限されます(getメソッドはOptional.ofNullable(field)を返します)。ただし、内部表現の場合、オプションを使用して、値がオプションであることを明確に示すことはできません。
vanarchi 14

私はそれ可能であることを示しました。何らかの理由で他に考えている場合、あなたの質問は正確には何ですか?
ローンの侯爵2014

あなたの答えをありがとう、それは私のためのオプションを追加します。私のコメントでは、このトピックについてさらに考えを示したかったので、各ソリューションの賛否両論を検討してください。使用方法のwriteObject / readObjectメソッドでは、状態の表現にOptionalの明確な意図がありますが、シリアル化の実装はより複雑になります。フィールドが計算/ストリームで集中的に使用される場合-writeObject / readObjectを使用する方が便利です。
vanarchi 14

コメントは、その下に表示される回答に関連している必要があります。あなたの黙想の関連性は率直に私をエスケープします。
ローンの侯爵2014

3

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

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

0

より一貫性のある型リストを維持し、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部材を、したがってすぎるシリアル変換。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.