Невозможно десериализовать лямбду
Так же, как небольшой проект, я пытался сделать крошечную вещь, которая читает сериализованные лямбды (локально или с FTP) и вызывает их функции запуска как часть теста, чтобы поэкспериментировать с ассоциациями файлов в Windows (то есть открыть определенные типы файлов). открывает их с помощью определенной программы) и еще много чего, но, что бы я ни пытался, он никогда не десериализуется должным образом.
Лямбда была объявлена так
Runnable r = (Runnable & Serializable) () -> {
// blah blah
// made sure not to capture anything
};
и сериализованный с использованием FileOutputStream, обернутый [n необязательным] BufferedOutputStream, обернутый ObjectOutputStream без проблем. Однако при десериализации [в другом проекте] он терпит неудачу, говоря, что не может найти включающий класс, содержащий код для его сериализации. Я пробовал разные вещи, такие как упаковка их в сериализуемый класс (w/serialVersionUID = 0L для целей тестирования) или определение интерфейса, расширяющего Runnable и Serializable, но безрезультатно.
Да, я знаю, что сериализация лямбд не является хорошей практикой (или нам так сказали), но я не уверен, как превратить функции и подпрограммы в то, что я могу сохранить в виде файла или на FTP. Если это даже не правильный путь, скажите.
О, я использую Eclipse Luna любой последней версии.
Редактировать:
Десериализовано как то так
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();
2 ответа
Вы не можете десериализовать объект без его определения классом. Это не изменилось с лямбда-выражениями.
Лямбда-выражения немного сложнее, так как их сгенерированный класс времени выполнения не является классом, который его определил, но их определяющим классом является тот, который содержит код тела лямбды и, в случае сериализуемых лямбд, метод поддержки десериализации, который вызывается для проверки и восстановление экземпляра лямбды.
Увидеть SerializedLambda
:
Ожидается, что разработчики сериализуемых лямбд, таких как компиляторы или языковые библиотеки времени выполнения, обеспечат правильную десериализацию экземпляров. Одним из способов сделать это является обеспечение того, чтобы
writeReplace
метод возвращает экземплярSerializedLambda
вместо того, чтобы продолжать сериализацию по умолчанию.
SerializedLambda
имеетreadResolve
метод, который ищет (возможно частный) статический метод, называемый$deserializeLambda$(SerializedLambda)
в классе захвата вызывает его с первым аргументом и возвращает результат. Реализация лямбда-классов$deserializeLambda$
несут ответственность за проверку того, что свойстваSerializedLambda
соответствуют лямбде, фактически захваченной этим классом.
Таким образом, даже если ваш экземпляр не ссылался на синтетический метод внутри определяющего класса (например, в случае ссылки на метод на метод вне этого класса), десериализация все еще требует $deserializeLambda$
для проверки правильности экземпляра, намеренно.
Что касается "хорошей практики" сериализации лямбд, имейте в виду, что лямбда-выражения инкапсулируют поведение, а не состояние. Хранение поведения всегда подразумевает хранение только некоторой ссылки и требует, чтобы код, предназначенный для его восстановления, реализовал соответствующее поведение. Это также сработало бы, если бы вы просто ссылались на предполагаемое поведение по символическому имени или просто сохраняли, например, ассоциировали enum
ценности.
Подробнее о последствиях использования сериализуемых лямбд объясняется в этом вопросе.
Когда вы десериализуете объект, код, выполняющий десериализацию, должен знать о классе сериализованного объекта. Вы не можете сериализовать произвольную лямбду и десериализовать ее в другой кодовой базе.
Более или менее код сериализации и десериализации должен находиться в одной и той же кодовой базе или, по крайней мере, должен иметь общую зависимость от кода, содержащего исходную лямбду.