To this end I created a simple unit test class that would take my filter, serialise it to disk, read it back and in then execute it. It had a instance field "VALUE" that we could use to reference directly or indirectly to find out what would cause the serialisation to fail.
import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.NotSerializableException; import java.io.ObjectInput; import java.io.ObjectInputStream; import java.io.ObjectOutput; import java.io.ObjectOutputStream; import java.io.Serializable; import java.util.function.Predicate; import org.junit.Test; public class SerializablePredicateFilterTest { public String VALUE = "Bob"; public interface SerializablePredicate<T> extends Predicate<T>, Serializable { } public <T> void filter(SerializablePredicate<T> sp, T value) throws IOException, ClassNotFoundException { sp.getClass().isLocalClass(); File tempFile = File.createTempFile("labmda", "set"); try (ObjectOutput oo = new ObjectOutputStream(new FileOutputStream(tempFile))) { oo.writeObject(sp); } try (ObjectInput oi = new ObjectInputStream(new FileInputStream(tempFile))) { SerializablePredicate<T> p = (SerializablePredicate<T>) oi.readObject(); System.out.println(p.test(value)); } } }
So just to calibrate lets make sure that an anonymous inner class will fail, because it will always contain a reference to enclosing object....
@Test(expected = NotSerializableException.class) public void testAnonymousDirect() throws IOException, ClassNotFoundException { String value = VALUE; filter(new SerializablePredicate<String>() { @Override public boolean test(String t) { return value.length() > t.length(); } }, "Bob"); }
The same is true for local classes, what you don't use local classes?
@Test(expected = NotSerializableException.class) public void testLocalClass() throws IOException, ClassNotFoundException { class LocalPredicate implements SerializablePredicate<String> { @Override public boolean test(String t) { // TODO Implement this method return false; } } filter(new LocalPredicate(), "Bobby"); }
So a standalone class will of course work, in this case a nested class for convenience.
public static class LengthPredicate implements SerializablePredicate<String> { private String value; public LengthPredicate(String value) { super(); this.value = value; } public void setValue(String value) { this.value = value; } public String getValue() { return value; } @Override public boolean test(String t) { // TODO Implement this method return false; } } @Test public void testStaticInnerClass() throws IOException, ClassNotFoundException { filter(new LengthPredicate(VALUE), "Bobby"); }
So lets get down with JDK 8, it turns out that my first try also fails but it does confirm that the serialisation is quite happy to take a Lambda in general.
@Test(expected = NotSerializableException.class) public void testLambdaDirect() throws IOException, ClassNotFoundException { filter((String s) -> VALUE.length() > s.length(), "Bobby"); }
A slight modification to copy the value into a effectively final attributes, and voila the lambda is now serialised and retrieved properly.
@Test public void testLambdaInDirect() throws IOException, ClassNotFoundException { String value = VALUE; filter((String s) -> value.length() > s.length(), "Bobby"); }
And of course if the value is a simple method parameter it also works fine.
@Test public void testLambdaParameter() throws IOException, ClassNotFoundException { invokeWithParameter(VALUE); } private void invokeWithParameter(String value) throws java.lang.ClassNotFoundException, java.io.IOException { filter((String s) -> value.length() > s.length(), "Bobby"); }
So the answer is yes, you can get it to serialise if you are a bit careful.
1 comment:
Actually, an anonymous inner class created from a static method will (obviously) have no reference to the enclosing instance - because there is no enclosing instance. Though this may be even more cumbersome than just using a named static inner class.
Post a Comment