Статьи

Лямбда, будет ли сериализация?

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

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

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.
@Test(expected = NotSerializableException.class)
02.
publicvoidtestAnonymousDirect() throwsIOException, ClassNotFoundException {
03.
 
04.
  String value = VALUE;
05.
 
06.
 
07.
  filter(newSerializablePredicate<String>() {
08.
 
09.
    @Override
10.
    publicbooleantest(String t) {
11.
      returnvalue.length() > t.length();
12.
    }
13.
  }, "Bob");
14.
 
15.
}

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

  @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");

  }

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

  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 в целом.

  @Test(expected = NotSerializableException.class)
  public void testLambdaDirect() throws IOException, ClassNotFoundException {


    filter((String s) -> VALUE.length() > s.length(), "Bobby");

  }

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

  @Test
  public void testLambdaInDirect() throws IOException, ClassNotFoundException {

    String value = VALUE;

    filter((String s) -> value.length() > s.length(), "Bobby");

  }

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

  @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");
  }

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