Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unable to deserialize lambda

Just as a small project, I've been trying to make a wee thing that reads serialized lambdas (locally or from an FTP) and invokes their run functions as part of a test to experiment with file associations in Windows (i.e. opening certain file types opens them with a certain program) and whatnot, but no matter what I try, it never seems to properly deserialize.

The lambda was declared like so

Runnable r = (Runnable & Serializable) () -> {
    // blah blah
    // made sure not to capture anything
};

and serialized using a FileOutputStream wrapped by a[n optional] BufferedOutputStream wrapped by an ObjectOutputStream without issue. However, when deserialized [in a different project], it fails, saying that it could not find the enclosing class that contained the code for serializing it. I've tried various things like wrapping them in a serializable class (w/serialVersionUID = 0L for testing purposes) or defining a an interface that extends Runnable and Serializable, but to no avail.

Yes, I am aware that serializing lambdas isn't really good practice (or so we're told), but I'm not sure how to go about turning functions and subroutines into something I can store as a file or in an FTP. If this isn't even the right way at all, do tell.

Oh, I'm using Eclipse Luna of whatever the latest version is.

Edit:

Deserialized like so

File f = new File(somePath);
FileInputStream fish = new FileInputStream(f);
BufferedInputStream bos = new BufferedInputStream(fish); // not really necessary
ObjectInputStream ois = new ObjectInputStream(bos);
Runnable r = (Runnable) ois.readObject();
ois.close();
r.run();
like image 305
Mona the Monad Avatar asked Jan 22 '15 00:01

Mona the Monad


2 Answers

You can’t deserialize an object without the class defining it. This hasn’t changed with lambda expressions.

Lambda expressions are a bit more complex as their generated runtime class is not the class which defined it but their defining class is the one holding the code of the lambda’s body and, in case of serializable lambdas, a deserialization support method which is called for validating and re- instantiating the lambda instance.

See SerializedLambda:

Implementors of serializable lambdas, such as compilers or language runtime libraries, are expected to ensure that instances deserialize properly. One means to do so is to ensure that the writeReplace method returns an instance of SerializedLambda, rather than allowing default serialization to proceed.

SerializedLambda has a readResolve method that looks for a (possibly private) static method called $deserializeLambda$(SerializedLambda) in the capturing class, invokes that with itself as the first argument, and returns the result. Lambda classes implementing $deserializeLambda$ are responsible for validating that the properties of the SerializedLambda are consistent with a lambda actually captured by that class.

So even if your instance was not referring to a synthetic method inside the defining class (e.g. in the case of a method reference to a method outside this class), deserialization still requires the $deserializeLambda$ for validating the correctness of the instance, intentionally.


Regarding the “good practice” of serializing lambdas, keep in mind that lambda expressions encapsulate behavior, not state. Storing behavior always implies storing just some kind of reference and requiring the code intended to restore it, to have implemented the associated behavior. That would work as well if you just referred to the intended behavior by a symbolic name or just stored, e.g. associated enum values.

More about the implications of having serializable lambdas is explained in this question.

like image 169
Holger Avatar answered Sep 21 '22 15:09

Holger


When you deserialize an object, the code doing the deserialization must know about the class of the serialized object. You cannot serialize an arbitrary lambda and deserialize it in another codebase.

More or less, the serializing and deserializing code must be in the same codebase, or at least must share a dependency on the code containing the original lambda.

like image 41
Louis Wasserman Avatar answered Sep 18 '22 15:09

Louis Wasserman