14

I can serialize a lambda with the following syntax:

Runnable r = (Runnable & Serializable) () -> System.out.println("");
try (ObjectOutput oo = new ObjectOutputStream(new ByteArrayOutputStream())) {
  oo.writeObject(r);
}

However if I receive the lambda from a client code and it has not been cast appropriately, I can't serialize it.

How can I serialize r below without changing its definition:

Runnable r = () -> System.out.println("");

I have tried to serialize a "derived" object:

Runnable r1 = (Runnable & Serializable) r::run;
Runnable r2 = (Runnable & Serializable) () -> r.run();

but in each case, oo.writeObject(rxxx); fails with a NotSerializableException.

Community
  • 1
  • 1
assylias
  • 321,522
  • 82
  • 660
  • 783
  • 2
    I don't believe this to be possible. – Louis Wasserman Aug 19 '14 at 20:00
  • Strangely, it seems to work if you assign r to a static Runnable field first. – Sean Van Gorder Aug 19 '14 at 22:54
  • 3
    @Sean Van Gorder: it “works” if `r` is a `static` field because the `static` field is not stored on Serialization which means that on Deserialization you will get a lambda which will just access `r`, regardless of what value it has then. – Holger Aug 20 '14 at 12:04

1 Answers1

16

This is correct, and by design. Just as you cannot take a non-serializable object and make it serializable after instantiation, once a lambda is created, its serializability is set.

A lambda is serializable if its target type is serializable (and its captured arguments are serializable.) Your first example is serializable because the target type is the intersection (Runnable & Serializable). Your two attempts to convert r fail because in both cases, r is a captured variable that is not serializable, and so the resulting lambda expression / method reference is not serializable. (The receiver for a bound method reference acts as a captured variable.)

Brian Goetz
  • 90,105
  • 23
  • 150
  • 161
  • 1
    What I really wish is the opposite, a way to opt out Serialization support if the target interface happened to inherit `Serializable` but I don’t want to support it for my lambda expression… – Holger Aug 20 '14 at 09:02
  • 1
    @Holger That's easy too; just provide a cast that does not include `Serializable`. The cast is not magic; it is a more explicit, expression-friendly way to provide a target type. And, if you've already got a serializable lambda and want to un-serializable it, you can use the tricks that assylias tried -- they'll effectively "wash off" the serializable marker. – Brian Goetz Aug 20 '14 at 15:17
  • 2
    if the target type already is `Serializable`, how can you “wash off” the serializable marker? Every re-wrapping in another lambda will again create a `Serializable` lambda, unless I use a different target type. But if I was able to use a different target type, I hadn’t the problem in the first place. I could forcibly add a captured non-`Serializable` value, but that still must be done very carefully to prevent attacks with a carefully designed serialized form. And it doesn’t allow to get rid of the overhead of the serializable lambda. – Holger Aug 20 '14 at 15:39
  • 1
    @Holger: You've asked enough that its an actual question now, far too much to fit in a comment ... :) – Brian Goetz Aug 20 '14 at 17:46