2

Let's say there's a class A with the lambda function named foo (for simplicity).

From my understanding, if there is a client serializing this foo (which would be serialized to "SerializedLambda") and send it to the server, the server would also have the class A with the lambda function named foo.

Now, my question is, what if the implementation of A.foo is different between the client and the server? (assuming the arg types and the return types are the same) Does the server

  1. deserialize "SerializedLambda" according to its own definition with no errors.

  2. fail to deserialize.

Given how lambdas are created dynamically and "SerializedLambda" itself just contains signature, arguments and etc instead of the actual code, I suspect it's 1, but I am a newbie who needs to learn more about how this works.

newbie
  • 65
  • 5

1 Answers1

4

Lambda expressions are compiled into synthetic methods with an unspecified name. There can be other unspecified subtleties, like the method’s argument order for captured variables. Further, when the lambda expression accesses the this instance, there are two options, compile it to an instance method or compile it to a static method receiving the instance like an ordinary parameter.

For the resulting behavior of the lambda expression, this makes no difference. But the serialized form depends on these details and hence, is very fragile. You don’t even need to change the class, recompiling it with a different compiler could change these details. In fact, even recompiling with the same compiler could change the outcome, e.g. when the compiler’s behavior depends on some hash map with iteration order randomization.

In practice, compiler vendors try to reduce such effects and produce stable results, even when not being mandated by the specification. But when you change the class, all bets are off. One aspect you can easily see in the compiled class file is that compilers add a number to the method name, depending on the order of occurrence in the source file. Inserting another lambda expression or removing one can change the numbers of all subsequent lambda expressions.

So, a changed A class can be incompatible to your serialized lambda expression, and the best thing that can happen, is an exception during deserialization due to a mismatch. Worse than that, it could select the wrong lambda expression, because it happened to have a compatible combination of name and signature.

A safer construct is method references, as they refer to the actual target method, which is not affected by other lambda expressions or method references within the same class. However, not every method reference is compiled as a direct reference on the byte code level. In certain cases, like when referring to a varargs method or calling a super method or a method on an intersection type whose declaring class does not match the erased type, the compiler may generate a stub method for the invocation, similar to a lambda expression. So you still have to be careful.

Holger
  • 285,553
  • 42
  • 434
  • 765
  • Thank you very much @Holger. What if I create a file from the content of the compiled class for A, and send that to the server where it adds to the (child-first) classloader's directory? Would it be possible to make method references safe for all the cases or is it not helpful for cases where stub methods are generated? I need stability since I do not have full control over the lambdas on the client side. – newbie Apr 27 '20 at 14:45
  • 1
    It’s not clear what you are trying to achieve. The decision to use lambda expressions or method references in a piece of code is an implementation detail. When you are assuming that the particular code could look entirely different on the other side, how are you assuming that it will still use exactly the same lambda expressions and method references? – Holger Apr 27 '20 at 14:51
  • Sorry I think I wasn't clear with my question. Here, lambda functions on the client and the server accomplishes the same thing but there can be minor differences. But like you pointed out earlier, this minor difference could fail SerializedLambda to invoke its impl on the server side. The follow-up question I had was, if I copy over the client side class A to the server side, and try to invoke lambda on that instead of the server side class A, could that work ? – newbie Apr 27 '20 at 15:11
  • 1
    If you want to send something which represents a strategy, the robustest way is via an `enum` type implementing an interface. The serialized form consists of type and constant name only and all other implementation details are irrelevant. The `enum` type could even encapsulate a lambda expression or method reference to delegate the actual work. – Holger Apr 27 '20 at 15:24
  • 2
    Copying an implementation class only works when it does not have dependencies to other local artifacts. Further, the setup must be able to cope with the presence of two `A` classes, e.g. if `A` has a dependency to class `B` which has a dependency to `A`, it doesn’t work. I’d try all other options first, before resorting to transferring class files. – Holger Apr 27 '20 at 15:27
  • I see.. unfortunately `enum` approach may not work well for me as there can be thousands of these lambda functions. So that was why I was hoping to do this via serialization/deserialization stage. – newbie Apr 27 '20 at 15:37
  • is it not possible to invoke server's static lambda implementation using class name, method type (arg types + return type) and lambda function name ? I guess the names can be different with random # attached to them.. – newbie Apr 27 '20 at 15:42
  • I just posted a follow-up question.. could you please take a look on it ? https://stackoverflow.com/questions/61462736/is-there-any-safe-way-to-serialize-deserialize-java-lambda-for-two-different-jre Greatly appreciated! – newbie Apr 27 '20 at 15:46