ラムダ式への参照を受け入れ、ラムダ式がステートレスかどうかを示すブール値を返す関数はありますか?ラムダ式のステートフルネスはどのように決定できますか?
ラムダ式は、メソッドが1つしかない特別な匿名クラスのインスタンスにすぎません。匿名クラスは、周囲のスコープにある変数を「capture」できます。ステートフルクラスの定義がそのフィールドに可変のものを運ぶものである場合(そうでなければ、それはほとんど定数です)、それは キャプチャがどのように実装されているように見えるか であるため、幸運です。ここに少し実験があります:
import Java.lang.reflect.Field;
import Java.util.function.Function;
public class Test {
public static void main(String[] args) {
final StringBuilder captured = new StringBuilder("foo");
final String inlined = "bar";
Function<String, String> lambda = x -> {
captured.append(x);
captured.append(inlined);
return captured.toString();
};
for (Field field : lambda.getClass().getDeclaredFields())
System.out.println(field);
}
}
出力は次のようになります。
private final Java.lang.StringBuilder Test$$Lambda$1/424058530.arg$1
StringBuilder
参照は、匿名ラムダクラス(およびfinal String inlined
定数は効率のためにインライン化されましたが、それは重要ではありません)。したがって、この関数はほとんどの場合に実行する必要があります。
public static boolean hasState(Function<?,?> lambda) {
return lambda.getClass().getDeclaredFields().length > 0;
}
編集:@Federicoが指摘しているように、これは実装固有の動作であり、一部のエキゾチックな環境やOracle/OpenJDK JVMの将来のバージョンでは機能しない可能性があります。
いいえ、通常はできません。ラムダがフィールドを持つクラスに属しているかどうかを確認するための推奨されるアプローチは次善の策ですが、フィールドを持つことは状態を持つことと同じではありません。
class Stateless {
int result = 0;
public int getResult() { return result; }
}
特定の入力の組み合わせが異なる結果を返す2つの入力シーケンスを見つけることにより、ステートフル性を証明することができます。ただし、そのような入力シーケンスが存在しないことを証明することはできません(別の呼び出しが前に付けられた場合、どの入力シーケンスでも異なる結果が生成される可能性があります)。
(リフレクションを介して検出されたフィールドの値を確認しても、ラムダの結果に影響を与えることなく変更される可能性があるため、実際にはステートフルにはなりません)。
これは、誤検知と誤検知の両方を示し、概念を反証する、コンパイル可能な短い例です。
public class StatefulLambda {
static AtomicInteger counter = new AtomicInteger();
public static void main(String[] args) {
// false negative: will return different result each call
System.out.println(hasState(i -> counter.incrementAndGet()));
// false positive: will always return the same result
Object object = new Object() {
final int i = 0;
};
System.out.println(hasState(i -> object.toString()));
}
private static boolean hasState(Function<?,?> lambda) {
return lambda.getClass().getDeclaredFields().length > 0;
}
}
これは単純で愚かな考えです。ラムダにフィールドがあるかどうかを確認してください。
たとえば、次のステートフルラムダについて考えてみます。
List<Integer> serialStorage = new ArrayList<>();
Function<? super Integer, ? extends Integer> statefulLambda =
e -> { serialStorage.add(e); return e; };
このstatefulLambda
には、明らかにserialStorage
を参照するプライベート最終内部フィールドarg$1
があります。そう
statefulLambda.getClass().getDeclaredFields().length > 0
ラムダがステートフルであることを示すインジケーターとして使用できます。
しかし、これが一般的に機能するかどうかはわかりません。