Determine if a lambda expression is stateless or stateful in Java

后端 未结 4 926
南旧
南旧 2021-02-01 20:01

Is there a function which accepts a reference to a lambda expression and returns a boolean saying whether the lambda expression is stateless or not? How can the statefulness of

相关标签:
4条回答
  • 2021-02-01 20:14

    Here's a simple and stupid idea. Just check if your lambda has fields.

    For instance, consider the following stateful lambda.

      List<Integer> serialStorage = new ArrayList<>();
      Function<? super Integer, ? extends Integer> statefulLambda =
          e -> { serialStorage.add(e); return e; };
    

    This statefulLambda has a private final internal field arg$1 which obviously references serialStorage. So

      statefulLambda.getClass().getDeclaredFields().length > 0
    

    could be used as indicator that lambda is stateful.

    However I have no idea if this will generally work.

    0 讨论(0)
  • 2021-02-01 20:15

    I would argue that it is not possible to write a function that can determine if a lambda is stateless or not:

    Looking for example at the filter method of the Stream API, the javadoc states that the parameter must be "a [...] stateless predicate" and also links to the API's definition of stateless.

    If there was a way to determine if the parameter of the filter (or any other) method was stateless or not, the Stream class would have included the possibility to throw an IllegalArgumentException in case the parameter was a stateful lambda. As this has not been implemented and only a warning was added to the javadocs, one can conclude that there is no way write a function that can determine if a lambda lambda is stateless.


    Edit (after reading the comments from Eric): There are plenty of situations where an implementation team makes implementation choices to not implement a particular feature; we usually cannot conclude from those choices that the feature is impossible for someone else to implement. In this special case, I believe its implausible that the Java 8 designers would not have found or done it if there was a (computational cheap) solution.

    0 讨论(0)
  • 2021-02-01 20:24

    No, it is not generally possible. The suggested approach of checking whether the lambda belongs to a class with a field is the next best thing, but having a field does not equal having a state.

    class Stateless {
        int result = 0;
        public int getResult() { return result; }
    }
    

    It is possible to prove statefulness by finding two input sequence for which a given input combination returns a different result. However, it is not possible to prove that such a input sequence does not exist (any input sequence might produce a different result if prepended by another invocation).

    (Even if you check the values of fields found via reflection, those might change without influencing the lambda's result, therefore not really making it stateful).

    Here's a short compilable example showing both false positive and negatives, disproving the notion:

    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;
        }
    }
    
    0 讨论(0)
  • 2021-02-01 20:34

    Well, a lambda expression is just an instance of a special anonymous class that only has one method. Anonymous classes can "capture" variables that are in the surrounding scope. If your definition of a stateful class is one that carries mutable stuff in its fields (otherwise it's pretty much just a constant), then you're in luck, because that's how capture seems to be implemented. Here is a little experiment :

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

    The output looks something like this :

    private final java.lang.StringBuilder Test$$Lambda$1/424058530.arg$1
    

    The StringBuilder reference got turned into a field of the anonymous lambda class (and the final String inlined constant was inlined for efficiency, but that's beside the point). So this function should do in most cases :

    public static boolean hasState(Function<?,?> lambda) {
        return lambda.getClass().getDeclaredFields().length > 0;
    }
    

    EDIT : as pointed out by @Federico this is implementation-specific behavior and might not work on some exotic environments or future versions of the Oracle / OpenJDK JVM.

    0 讨论(0)
提交回复
热议问题