обзор
Сериализация лямбд может быть полезна в ряде случаев, таких как сохранение конфигурации или в качестве шаблона посетителя для удаленных ресурсов.
Удаленные посетители
Например, так что я хочу получить доступ к ресурсу на удаленной карте, я могу использовать get / put, но, скажем, я просто хочу вернуть поле из значения карты, я могу передать лямбду в качестве посетителя для извлечения информации Я хочу.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
|
MapView userMap = Chassis.acquireMap( "users" , String. class , UserInfo. class ); userMap.put( "userid" , new UserInfo( "User's Name" )); // print out changes userInfo.registerSubscriber(System.out::println); // obtain just the fullName without downloading the whole object String name= userMap.applyToKey( "userid" , u -> u.fullName); // increment a counter atomically and trigger // an updated event printed with the subscriber. userMap.asyncUpdateKey( "userid" , ui -> { ui.usageCounter++; return ui; }); // increment a counter and return the userid int count = userMap.syncUpdateKey( "userid" , ui -> { ui.usageCounter++; return ui;}, ui -> ui.usageCounter); |
Как видите, легко добавлять различные простые функции или вызывать метод для выполнения нужного вам действия. Единственная проблема заключается в том, что лямбды по умолчанию не сериализуемы.
Сериализуемые лямбды
Простой способ сделать лямбда-сериализуемый — добавить преобразование & Serializable к переменной, ссылающейся на реализацию лямбда-выражения.
1
2
|
Function<UserInfo, String> fullNameFunc = (Function<UserInfo,String> & Serializable) ui -> ui.fullName; String fullName = userInfo.applyToKey( "userid" , fullNameFunc); |
Как вы можете видеть это вводит много котельной плиты. Ключевой причиной использования лямбд является отказ от кода котельной плиты, так какова альтернатива?
Делаем лямбды сериализуемыми в вашем API.
К сожалению, стандартные API не могут быть изменены или подклассы, чтобы добавить это, но если у вас есть собственный API, вы можете использовать интерфейс Serializable.
1
2
3
|
@FunctionalInterface public interface SerializableFunction<I, O> extends Function<I, O>, Serializable { } |
Этот интерфейс может использоваться как тип параметра.
1
2
3
|
default <R> R applyToKey(K key, @NotNull SerializableFunction<E, R> function) { return function.apply(get(key)); } |
Пользователь вашего API не должен явно говорить, что лямбда-сериализуема.
1
2
|
// obtain just the fullName without downloading the whole object String name= userMap.applyToKey( "userid" , u -> u.fullName); |
Удаленная реализация сериализует лямбду, выполняет ее на сервере и возвращает результат.
Точно так же есть способы применения лямбды к карте в целом.
Запрос и подписка
Для поддержки запросов вы не можете использовать встроенный API stream (), если хотите неявно добавить Serializable. Тем не менее, вы можете создать такой, который будет максимально похож.
1
2
3
4
|
Map> collect = userMap.entrySet().query() .filter(e -> e.getKey().matches( "u*d" )) .map(e -> e.getValue()) .collect(Collectors.groupingBy(u -> u.usageCounter)); |
или как отфильтрованная подписка.
1
2
3
4
|
// print userid which have a usageCounter > 10 each time it is incremented. userMap.entrySet().query() .filter(e -> e.getValue().usageCounter > 10 ) .map(e -> e.getKey()) .subscribe(System.out::println); |
Что отличает это от обычного потокового API, так это то, что данные могут быть распределены по многим серверам, и вы получаете обратный вызов, когда это меняется на любом сервере. Только данные, которые вас интересуют, отправляются по сети, так как фильтр и карта применяются на сервере.
Сериализация Java
Java Serialization — это хорошая обобщенная, обратно совместимая библиотека сериализации. Две наиболее распространенные проблемы, которые пытаются решить альтернативы, — это производительность и кроссплатформенная сериализация.
В приведенном выше примере fullNameFunc сериализуется до более чем 700 байтов, и есть очень ограниченные возможности для его оптимизации, чтобы уменьшить размер сообщения или объем создаваемого мусора. Для сравнения: прямая двоичная сериализация YAML использует 348 с большим количеством опций для оптимизации сериализации.
Это поднимает проблему того, как сериализовать лямбду, используя альтернативный или кроссплатформенный или более быстрый формат сериализации.
Альтернативная Сериализация
Вы можете подключиться к текущему механизму сериализации. Это не поддерживается и может измениться в любое время, но другого поддерживаемого способа сделать это не существует.
Тем не менее, вы можете сделать это:
1
2
3
4
|
Method writeReplace = lambda.getClass() .getDeclaredMethod( "writeReplace" ); writeReplace.setAccessible( true ); SerializedLambda sl = (SerializedLambda) writeReplace.invoke(lambda); |
Это дает вам объект, который вы можете проверить, чтобы извлечь содержимое лямбды. Либо посмотреть, какой метод он вызывает, либо сериализовать его. На стороне десериализации вы можете воссоздать этот объект и можете прочитатьResolve для этого объекта.
Стандартный API
В настоящее время не существует стандартного API для самоанализа лямбды. Это сделано намеренно, чтобы в будущем реализация могла быть изменена, хотя для этого нет общедоступного JEP. Однако, подобно Unsafe, который является внутренним API, я с нетерпением жду того дня, когда мы сможем использовать стандартный API вместо того, чтобы копаться во внутренностях JVM для реализации решений.
Выводы
С некоторыми изменениями в вашем API вы можете сделать лямбды-сериалы в значительной степени прозрачными для разработчика. Это облегчает использование простых распределенных систем и дает возможность оптимизировать, как это делается.
Ссылка: | Как и зачем сериализовать Lambdas от нашего партнера JCG Питера Лоури из блога Vanilla Java . |