2016-06-24 22 views
8

it is possible to serialize a lambda in Java 8でも、それはstrongly discouragedです。さらにserializing inner classes is discouraged。その理由は、lambdaが別のJREで正しくデシリアライズできない可能性があるからです。しかし、これは、ラムダを安全にシリアル化する方法であることを意味していませんか?ラムダを安全にシリアル化する方法は?

public class MyClass { 
    private String value; 
    private Predicate<String> validateValue; 

    public MyClass(String value, Predicate<String> validate) { 
     this.value = value; 
     this.validateValue = validate; 
    } 

    public void setValue(String value) { 
     if (!validateValue(value)) throw new IllegalArgumentException(); 
     this.value = value; 
    } 

    public void setValidation(Predicate<String> validate) { 
     this.validateValue = validate; 
    } 
} 

私はこのようなクラスのインスタンスを宣言した場合、私はそれをシリアルべきではない:

MyClass obj = new MyClass("some value", (s) -> !s.isEmpty()); 

しかし、例えば、私はこのようなものにするためにクラスを定義

を言います

// Could even be a static nested class 
public class IsNonEmpty implements Predicate<String>, Serializable { 
    @Override 
    public boolean test(String s) { 
     return !s.isEmpty(); 
    } 
} 
:私はこのようなクラスのインスタンスを作ったものならば210
MyClass isThisSafeToSerialize = new MyClass("some string", new IsNonEmpty()); 

これでシリアル化するのが安全ですか?私の本能はそう言います、安全でなければなりません。なぜなら、java.util.functionのインターフェースは、他の任意のインターフェースとは異なった扱いをする必要がないからです。しかし、私はまだ注意しています。

+1

インターフェイスはシリアライゼーションとは完全に無関係です。したがって、「述語」を実装すると他のインターフェイスを実装するのと同じ影響があります。しかし、ラムダが別のJREで正しく非直列化できないというあなたの前提は間違っています。[明確な永続表現](https://docs.oracle.com/javase/8/docs/api/?java/lang/invoke/SerializedLambda.html)があります。 – Holger

+0

@Holger次に、[oracle docs](https://docs.oracle.com/javase/tutorial/java/javaOO/lambdaexpressions.html#serialization)が、そうでないことを示唆しているのはなぜですか? – Justin

+1

まあ、それには[シリアライズに関する内部クラス関連の問題](https://docs.oracle.com/javase/tutorial/java/jested/nested.html#serialization)への参照が含まれています。要するに、JRE固有の問題ではなく、コンパイラの依存関係を作成する可能性があります。確かに、テキストは少し誤解を招くことがあります。そして、 'this'を含む周囲の文脈の捕捉された値を誤って連載する危険に気をつけてください... – Holger

答えて

9

安全性の種類によって異なります。シリアライズされたラムダを異なるJRE間で共有することはできません。彼らはよく定義された永続的表現、SerializedLambdaを持っています。あなたが勉強するとき、それがどのように動作するかは、ラムダを再構成する特別なメソッドを持つ定義クラスの存在に依存していることがわかります。

コンパイラ固有のアーティファクトへの依存性が信頼できないものになるのはなぜですか?生成された名前がいくつかある合成ターゲットメソッドは、別のラムダ式を挿入するか、別のコンパイラでクラスを再コンパイルするなどの単純な変更によって、既存のシリアライズされたラムダ式との互換性が失われる可能性があります。

ただし、手作業で作成したクラスを使用することは、これに対して免除されません。明示的にserialVersionUIDと宣言されていなければ、デフォルトのアルゴリズムは、privateなどのクラス成果物をハッシュすることによってidを計算し、同様のコンパイラ依存性を追加します。だから最小限にするには、信頼できる永続的なフォームが必要な場合は、明示的に宣言することですserialVersionUID

それとも、可能な限り最も堅牢な形式に回す:この定数をシリアル化

public enum IsNonEmpty implements Predicate<String> { 
    INSTANCE; 

    @Override 
    public boolean test(String s) { 
     return !s.isEmpty(); 
    } 
} 

はもちろん、そのクラス名(と、それはenumであるという事実以外に、実際の実装の任意のプロパティを格納していません)と定数の名前への参照。デシリアライズ時に、その名前の実際の一意のインスタンスが使用されます。


serializable lambda expressions may create security issues、彼らがターゲットメソッドを呼び出すことができますオブジェクトの上に手を取得する別の道を開くために注意してください。ただし、これはすべてのシリアライズ可能なクラスに適用されます。これは、質問に示されているすべてのバリアントと、この応答でカプセル化された操作を呼び出すことができるオブジェクトを意図的に逆シリアル化できます。しかし、明示的にシリアライズ可能なクラスでは、作者は通常、この事実をよりよく知っています。

+0

私はenumでどんな危険も見ません。シリアライズすると、基本的にはクラス名とインスタンス名だけになります。それはどのようにセキュリティの問題ですか?問題は基本的に、攻撃者がそれが起こるつもりがないときにINSTANCEにアクセスできるかどうかです。 – Justin

+2

正確に。クラスをシリアライズ可能にすることは、クラス自体が 'public'でなくても使用できる' public'コンストラクタ(またはアクセサ)を追加するようなものです。 'Predicate'のような共通のインターフェースと組み合わせて、カプセル化された操作へのアクセスを提供することを意味します。操作自体が重要ではない場合、問題はありません。 – Holger

関連する問題