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