Недавно я столкнулся со следующей ошибкой при сериализации лямбды с помощью Kryo:
com.esotericsoftware.kryo.KryoException: java.lang.IllegalArgumentException: Unable to serialize Java Lambda expression, unless explicitly declared e.g., Runnable r = (Runnable & Serializable) () -> System.out.println("Hello world!");
Если вы не распознаете синтаксис (Запускаемый и сериализуемый)
, не волнуйтесь, он просто указывает, что лямбда-код должен реализовывать два типа. Это называется Пересечение типов . Лично мне самому никогда не приходилось этим пользоваться, поэтому я никогда по-настоящему об этом не задумывался. Сериализуемый
– это немного уникальный интерфейс в этом отношении, так как на самом деле вам ничего не нужно реализовывать.
Без этого приведения лямбда будет считаться несериализуемой, что не делает Крио счастливым.
Как человек, который не очень часто смотрит на байт-код, я нахожу удивительным, насколько велика разница при добавлении и дополнительном приведении & Сериализуемого
. Приведенные ниже примеры демонстрируют это. Для ясности я использовал следующую команду для генерации байт-кода из фрагментов кода:
javap -c -p target.classes.dev.lankydan.IntersectionCasting
Прежде чем делать какой-либо кастинг:
public class IntersectionCasting { public static void main(String[] args) { Functionfunction = (message) -> "Kryo please serialize this message '" + message + "'"; } }
Сгенерированный байт-код является:
public class dev.lankydan.IntersectionCasting { public dev.lankydan.IntersectionCasting(); Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Object."":()V 4: return public static void main(java.lang.String[]); Code: 0: invokedynamic #2, 0 // InvokeDynamic #0:apply:()Ljava/util/function/Function; 5: astore_1 6: return private static java.lang.String lambda$main$0(java.lang.String); Code: 0: new #3 // class java/lang/StringBuilder 3: dup 4: invokespecial #4 // Method java/lang/StringBuilder." ":()V 7: ldc #5 // String Kryo please serialize this message ' 9: invokevirtual #6 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 12: aload_0 13: invokevirtual #6 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 16: ldc #7 // String ' 18: invokevirtual #6 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 21: invokevirtual #8 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 24: areturn }
После отливки:
public class IntersectionCasting { public static void main(String[] args) { Functionfunction = (Function & Serializable) (message) -> "Kryo please serialize this message '" + message + "'"; } }
Байт-код становится:
public class dev.lankydan.IntersectionCasting { public dev.lankydan.IntersectionCasting(); Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Object."":()V 4: return public static void main(java.lang.String[]); Code: 0: invokedynamic #2, 0 // InvokeDynamic #0:apply:()Ljava/util/function/Function; 5: checkcast #3 // class java/io/Serializable 8: checkcast #4 // class java/util/function/Function 11: astore_1 12: return private static java.lang.Object $deserializeLambda$(java.lang.invoke.SerializedLambda); Code: 0: aload_0 1: invokevirtual #5 // Method java/lang/invoke/SerializedLambda.getImplMethodName:()Ljava/lang/String; 4: astore_1 5: iconst_m1 6: istore_2 7: aload_1 8: invokevirtual #6 // Method java/lang/String.hashCode:()I 11: lookupswitch { // 1 -1657128837: 28 default: 39 } 28: aload_1 29: ldc #7 // String lambda$main$2cf54983$1 31: invokevirtual #8 // Method java/lang/String.equals:(Ljava/lang/Object;)Z 34: ifeq 39 37: iconst_0 38: istore_2 39: iload_2 40: lookupswitch { // 1 0: 60 default: 135 } 60: aload_0 61: invokevirtual #9 // Method java/lang/invoke/SerializedLambda.getImplMethodKind:()I 64: bipush 6 66: if_icmpne 135 69: aload_0 70: invokevirtual #10 // Method java/lang/invoke/SerializedLambda.getFunctionalInterfaceClass:()Ljava/lang/String; 73: ldc #11 // String java/util/function/Function 75: invokevirtual #12 // Method java/lang/Object.equals:(Ljava/lang/Object;)Z 78: ifeq 135 81: aload_0 82: invokevirtual #13 // Method java/lang/invoke/SerializedLambda.getFunctionalInterfaceMethodName:()Ljava/lang/String; 85: ldc #14 // String apply 87: invokevirtual #12 // Method java/lang/Object.equals:(Ljava/lang/Object;)Z 90: ifeq 135 93: aload_0 94: invokevirtual #15 // Method java/lang/invoke/SerializedLambda.getFunctionalInterfaceMethodSignature:()Ljava/lang/String; 97: ldc #16 // String (Ljava/lang/Object;)Ljava/lang/Object; 99: invokevirtual #12 // Method java/lang/Object.equals:(Ljava/lang/Object;)Z 102: ifeq 135 105: aload_0 106: invokevirtual #17 // Method java/lang/invoke/SerializedLambda.getImplClass:()Ljava/lang/String; 109: ldc #18 // String dev/lankydan/IntersectionCasting 111: invokevirtual #12 // Method java/lang/Object.equals:(Ljava/lang/Object;)Z 114: ifeq 135 117: aload_0 118: invokevirtual #19 // Method java/lang/invoke/SerializedLambda.getImplMethodSignature:()Ljava/lang/String; 121: ldc #20 // String (Ljava/lang/String;)Ljava/lang/String; 123: invokevirtual #12 // Method java/lang/Object.equals:(Ljava/lang/Object;)Z 126: ifeq 135 129: invokedynamic #2, 0 // InvokeDynamic #0:apply:()Ljava/util/function/Function; 134: areturn 135: new #21 // class java/lang/IllegalArgumentException 138: dup 139: ldc #22 // String Invalid lambda deserialization 141: invokespecial #23 // Method java/lang/IllegalArgumentException." ":(Ljava/lang/String;)V 144: athrow private static java.lang.String lambda$main$2cf54983$1(java.lang.String); Code: 0: new #24 // class java/lang/StringBuilder 3: dup 4: invokespecial #25 // Method java/lang/StringBuilder." ":()V 7: ldc #26 // String Kryo please serialize this message ' 9: invokevirtual #27 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 12: aload_0 13: invokevirtual #27 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 16: ldc #28 // String ' 18: invokevirtual #27 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 21: invokevirtual #29 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 24: areturn }
Теперь я действительно не знаю, как читать байт-код, но даже я вижу, что в версии с приведением & сериализуемым
происходит гораздо больше.
Если вы можете объяснить, что там происходит, тогда будьте моим гостем и дайте мне знать.
Одна вещь, которую я могу прочитать из байт-кода выше, – это ссылки на Сериализованную Лямбду
, которых раньше не было. Этот класс используется компиляторами и библиотеками для обеспечения правильной десериализации лямбд. Выполнение приведения к пересечению функции<Строка, строка> & Сериализуемой
изменяет базовый тип лямбды, позволяя библиотеке, такой как Kryo, правильно понимать, как десериализовать предоставленные ей лямбды.
Добавление этого дополнительного приведения & Сериализуемого
является одним из возможных решений, позволяющих Kryo десериализовать лямбды. Альтернативный маршрут предполагает создание нового интерфейса, который расширяет как базовый Функция
тип, который вам нужен, так и Сериализуемый
. Это полезно при создании библиотеки или API для использования другими пользователями. Позволяя им сосредоточиться исключительно на реализации своего кода, а не беспокоиться о том, чтобы обеспечить правильное приведение для удовлетворения сериализации их лямбд.
Вы могли бы использовать интерфейс, подобный приведенному ниже:
interface SerializableLambda extends Function, Serializable {}
Затем это можно использовать для замены приведения в предыдущем примере:
public class IntersectionCasting { public static void main(String[] args) { SerializableLambda function = (message) -> "Kryo please serialize this message '" + message + "'"; } interface SerializableLambda extends Function, Serializable {} }
Я добавил байт-код, сгенерированный этим изменением ниже:
public class dev.lankydan.IntersectionCasting { public dev.lankydan.IntersectionCasting(); Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Object."":()V 4: return public static void main(java.lang.String[]); Code: // NO CASTING // Mention of casting is removed and reference to new interface is added 0: invokedynamic #2, 0 // InvokeDynamic #0:apply:()Ldev/lankydan/IntersectionCasting$SerializableLambda; 5: astore_1 6: return private static java.lang.Object $deserializeLambda$(java.lang.invoke.SerializedLambda); Code: 0: aload_0 1: invokevirtual #3 // Method java/lang/invoke/SerializedLambda.getImplMethodName:()Ljava/lang/String; 4: astore_1 5: iconst_m1 6: istore_2 7: aload_1 8: invokevirtual #4 // Method java/lang/String.hashCode:()I 11: lookupswitch { // 1 -1657128837: 28 default: 39 } 28: aload_1 29: ldc #5 // String lambda$main$2cf54983$1 31: invokevirtual #6 // Method java/lang/String.equals:(Ljava/lang/Object;)Z 34: ifeq 39 37: iconst_0 38: istore_2 39: iload_2 40: lookupswitch { // 1 0: 60 default: 135 } 60: aload_0 61: invokevirtual #7 // Method java/lang/invoke/SerializedLambda.getImplMethodKind:()I 64: bipush 6 66: if_icmpne 135 69: aload_0 70: invokevirtual #8 // Method java/lang/invoke/SerializedLambda.getFunctionalInterfaceClass:()Ljava/lang/String; 73: ldc #9 // String dev/lankydan/IntersectionCasting$SerializableLambda 75: invokevirtual #10 // Method java/lang/Object.equals:(Ljava/lang/Object;)Z 78: ifeq 135 81: aload_0 82: invokevirtual #11 // Method java/lang/invoke/SerializedLambda.getFunctionalInterfaceMethodName:()Ljava/lang/String; 85: ldc #12 // String apply 87: invokevirtual #10 // Method java/lang/Object.equals:(Ljava/lang/Object;)Z 90: ifeq 135 93: aload_0 94: invokevirtual #13 // Method java/lang/invoke/SerializedLambda.getFunctionalInterfaceMethodSignature:()Ljava/lang/String; 97: ldc #14 // String (Ljava/lang/Object;)Ljava/lang/Object; 99: invokevirtual #10 // Method java/lang/Object.equals:(Ljava/lang/Object;)Z 102: ifeq 135 105: aload_0 106: invokevirtual #15 // Method java/lang/invoke/SerializedLambda.getImplClass:()Ljava/lang/String; 109: ldc #16 // String dev/lankydan/IntersectionCasting 111: invokevirtual #10 // Method java/lang/Object.equals:(Ljava/lang/Object;)Z 114: ifeq 135 117: aload_0 118: invokevirtual #17 // Method java/lang/invoke/SerializedLambda.getImplMethodSignature:()Ljava/lang/String; 121: ldc #18 // String (Ljava/lang/String;)Ljava/lang/String; 123: invokevirtual #10 // Method java/lang/Object.equals:(Ljava/lang/Object;)Z 126: ifeq 135 129: invokedynamic #2, 0 // InvokeDynamic #0:apply:()Ldev/lankydan/IntersectionCasting$SerializableLambda; 134: areturn 135: new #19 // class java/lang/IllegalArgumentException 138: dup 139: ldc #20 // String Invalid lambda deserialization 141: invokespecial #21 // Method java/lang/IllegalArgumentException." ":(Ljava/lang/String;)V 144: athrow private static java.lang.String lambda$main$2cf54983$1(java.lang.String); Code: 0: new #22 // class java/lang/StringBuilder 3: dup 4: invokespecial #23 // Method java/lang/StringBuilder." ":()V 7: ldc #24 // String Kryo please serialize this message ' 9: invokevirtual #25 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 12: aload_0 13: invokevirtual #25 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 16: ldc #26 // String ' 18: invokevirtual #25 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 21: invokevirtual #27 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 24: areturn }
Большая часть из них та же самая, с некоторыми новыми ссылками на Сериализуемый интерфейс Lambda
и удаление исходного приведения пересечения.
Как упоминалось ранее, это решение идеально подходит для авторов библиотек и API, поскольку оно позволяет разработчикам писать код в обычном режиме, не беспокоясь о приведении (например, если библиотека использует Kryo под капотом). Кроме того, поскольку интерфейс расширяет Функцию
, которая является @FunctionalInterface
, разработчики могут красиво писать лямбды/функции и даже не должны упоминать интерфейс, передавая его непосредственно в другую функцию или конструктор. Я лично пошел по этому пути при разработке нового API для Corda . Я хотел предоставить наиболее доступный API для разработчиков, в то же время предоставляя работающий API (я не могу позволить Kryo взорваться …).
В заключение, в этом посте, в котором не хватает большого количества информации и который усеян расширенными фрагментами байт-кода, вам нужно убрать две вещи. Вы можете сделать Java-лямбда/функцию сериализуемой с помощью пересечения типов, и вы можете убедиться, что ваши собственные API являются чистыми, создав новый интерфейс, который расширяет как желаемый тип функции, так и Сериализуемый
. Это оба маршрута, которые следует учитывать при использовании библиотеки сериализации, такой как Kryo.
Если вам понравился этот пост или вы сочли его полезным (или и то, и другое), пожалуйста, не стесняйтесь подписываться на меня в Твиттере по адресу @LankyDanDev и не забудьте поделиться с кем-либо еще, кто может счесть это полезным!
Оригинал: “https://dev.to/lankydandev/serializable-java-lambdas-3aog”