Статьи

Лямбда, он будет сериализоваться?

Поэтому я размышлял над усовершенствованием, необходимым для проекта Tyrus, которое позволило бы пользователю транслировать данные на подмножество клиентов, подключенных к URL-адресу на кластере машин. Есть разные способы сделать это; но так как я играл с JDK 8, эта проблема определенно выглядела как гвоздь.

С этой целью я создал простой класс модульного тестирования, который будет брать мой фильтр, сериализовать его на диск, читать обратно и затем выполнять. У него было поле экземпляра «VALUE», которое мы могли использовать для прямой или косвенной ссылки, чтобы выяснить, что может вызвать сбой сериализации.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
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));
    }
 
  }
 
}

Так что просто для калибровки давайте удостоверимся, что анонимный внутренний класс потерпит неудачу, потому что он всегда будет содержать ссылку на включающий объект….

01
02
03
04
05
06
07
08
09
10
11
12
13
14
@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");
 
  }

То же самое верно для локальных классов, что вы не используете локальные классы?

01
02
03
04
05
06
07
08
09
10
11
12
13
14
@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");
 
  }

Таким образом, автономный класс, конечно, будет работать, в данном случае для удобства — вложенный.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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");
 
  }

Итак, давайте вернемся к JDK 8, оказалось, что моя первая попытка также не удалась, но это подтверждает, что сериализация вполне устраивает Lambda в целом.

1
2
3
4
5
6
@Test(expected = NotSerializableException.class)
  public void testLambdaDirect() throws IOException, ClassNotFoundException {
 
    filter((String s) -> VALUE.length() > s.length(), "Bobby");
 
  }

Небольшая модификация для копирования значения в эффективно конечные атрибуты, и вот лямбда теперь сериализуется и извлекается правильно.

1
2
3
4
5
6
7
8
@Test
  public void testLambdaInDirect() throws IOException, ClassNotFoundException {
 
    String value = VALUE;
 
    filter((String s) -> value.length() > s.length(), "Bobby");
 
  }

И, конечно, если значение является параметром простого метода, оно также работает нормально.

01
02
03
04
05
06
07
08
09
10
@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");
  }

Так что ответ — да, вы можете заставить его сериализоваться, если вы немного осторожны.