Иногда можно услышать такие разговоры: никаких принципиальных изменений в Java 8 не произошло и лямбды — это старые добрые анонимные классы, щедро посыпанные синтаксическим сахаром.
Как бы там ни было! Сегодня я предлагаю поговорить о разнице между лямбдами и анонимными классами.
И почему стало сложнее ударить себя по ноге? Чтобы не терять время у тех, кто думает, что уже освоил анонимные функции, вот простая задача.
В чем разница между двумя фрагментами кода ниже:
И второй фрагмент:public class AnonymousClass { public Runnable getRunnable() { return new Runnable() { @Override public void run() { System.out.println("I am a Runnable!"); } }; } public static void main(String[] args) { new AnonymousClass().
getRunnable().
run(); } }
public class Lambda {
public Runnable getRunnable() {
return () -> System.out.println("I am a Runnable!");
}
public static void main(String[] args) {
new Lambda().
getRunnable().
run();
}
}
Если можете ответить сразу, решайте сами, хотите ли читать дальше.
Давайте декомпилируем
Давайте посмотрим на байт-код для обоих вариантов.(Подробная декомпиляция с флажком -подробный - под спойлером.
) С анонимным классом Compiled from "AnonymousClass.java"
public class AnonymousClass {
public AnonymousClass();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public java.lang.Runnable getRunnable();
Code:
0: new #2 // class AnonymousClass$1
3: dup
4: aload_0
5: invokespecial #3 // Method AnonymousClass$1."<init>":(LAnonymousClass;)V
8: areturn
public static void main(java.lang.String[]);
Code:
0: new #4 // class AnonymousClass
3: dup
4: invokespecial #5 // Method "<init>":()V
7: invokevirtual #6 // Method getRunnable:()Ljava/lang/Runnable;
10: invokeinterface #7, 1 // InterfaceMethod java/lang/Runnable.run:()V
15: return
}
RunnableAnonymousClassExperiment.class (подробная декомпиляция) Classfile /E:/.
/src/main/java/AnonymousClass.class
Last modified 17.10.2016; size 518 bytes
MD5 checksum cf61f38da50d7062537edefea71995dc
Compiled from "AnonymousClass.java"
public class AnonymousClass
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #8.#20 // java/lang/Object."<init>":()V
#2 = Class #21 // AnonymousClass$1
#3 = Methodref #2.#22 // AnonymousClass$1."<init>":(LAnonymousClass;)V
#4 = Class #23 // AnonymousClass
#5 = Methodref #4.#20 // AnonymousClass."<init>":()V
#6 = Methodref #4.#24 // AnonymousClass.getRunnable:()Ljava/lang/Runnable;
#7 = InterfaceMethodref #25.#26 // java/lang/Runnable.run:()V
#8 = Class #27 // java/lang/Object
#9 = Utf8 InnerClasses
#10 = Utf8 <init>
#11 = Utf8 ()V
#12 = Utf8 Code
#13 = Utf8 LineNumberTable
#14 = Utf8 getRunnable
#15 = Utf8 ()Ljava/lang/Runnable;
#16 = Utf8 main
#17 = Utf8 ([Ljava/lang/String;)V
#18 = Utf8 SourceFile
#19 = Utf8 AnonymousClass.java
#20 = NameAndType #10:#11 // "<init>":()V
#21 = Utf8 AnonymousClass$1
#22 = NameAndType #10:#28 // "<init>":(LAnonymousClass;)V
#23 = Utf8 AnonymousClass
#24 = NameAndType #14:#15 // getRunnable:()Ljava/lang/Runnable;
#25 = Class #29 // java/lang/Runnable
#26 = NameAndType #30:#11 // run:()V
#27 = Utf8 java/lang/Object
#28 = Utf8 (LAnonymousClass;)V
#29 = Utf8 java/lang/Runnable
#30 = Utf8 run
{
public AnonymousClass();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 1: 0
public java.lang.Runnable getRunnable();
descriptor: ()Ljava/lang/Runnable;
flags: ACC_PUBLIC
Code:
stack=3, locals=1, args_size=1
0: new #2 // class AnonymousClass$1
3: dup
4: aload_0
5: invokespecial #3 // Method AnonymousClass$1."<init>":(LAnonymousClass;)V
8: areturn
LineNumberTable:
line 3: 0
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=1, args_size=1
0: new #4 // class AnonymousClass
3: dup
4: invokespecial #5 // Method "<init>":()V
7: invokevirtual #6 // Method getRunnable:()Ljava/lang/Runnable;
10: invokeinterface #7, 1 // InterfaceMethod java/lang/Runnable.run:()V
15: return
LineNumberTable:
line 12: 0
line 13: 15
}
SourceFile: "AnonymousClass.java"
InnerClasses:
#2; //class AnonymousClass$1
С лямбдой Compiled from "Lambda.java"
public class Lambda {
public Lambda();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public java.lang.Runnable getRunnable();
Code:
0: invokedynamic #2, 0 // InvokeDynamic #0:run:()Ljava/lang/Runnable;
5: areturn
public static void main(java.lang.String[]);
Code:
0: new #3 // class Lambda
3: dup
4: invokespecial #4 // Method "<init>":()V
7: invokevirtual #5 // Method getRunnable:()Ljava/lang/Runnable;
10: invokeinterface #6, 1 // InterfaceMethod java/lang/Runnable.run:()V
15: return
}
Lambda.class (подробная декомпиляция) Classfile /E:/.
/src/main/java/Lambda.class
Last modified 17.10.2016; size 1095 bytes
MD5 checksum f09061410dfbe358c50880576557b64e
Compiled from "Lambda.java"
public class Lambda
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #10.#22 // java/lang/Object."<init>":()V
#2 = InvokeDynamic #0:#27 // #0:run:()Ljava/lang/Runnable;
#3 = Class #28 // Lambda
#4 = Methodref #3.#22 // Lambda."<init>":()V
#5 = Methodref #3.#29 // Lambda.getRunnable:()Ljava/lang/Runnable;
#6 = InterfaceMethodref #30.#31 // java/lang/Runnable.run:()V
#7 = Fieldref #32.#33 // java/lang/System.out:Ljava/io/PrintStream;
#8 = String #34 // I am a Runnable!
#9 = Methodref #35.#36 // java/io/PrintStream.println:(Ljava/lang/String;)V
#10 = Class #37 // java/lang/Object
#11 = Utf8 <init>
#12 = Utf8 ()V
#13 = Utf8 Code
#14 = Utf8 LineNumberTable
#15 = Utf8 getRunnable
#16 = Utf8 ()Ljava/lang/Runnable;
#17 = Utf8 main
#18 = Utf8 ([Ljava/lang/String;)V
#19 = Utf8 lambda$getRunnable$0
#20 = Utf8 SourceFile
#21 = Utf8 Lambda.java
#22 = NameAndType #11:#12 // "<init>":()V
#23 = Utf8 BootstrapMethods
#24 = MethodHandle #6:#38 // invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
#25 = MethodType #12 // ()V
#26 = MethodHandle #6:#39 // invokestatic Lambda.lambda$getRunnable$0:()V
#27 = NameAndType #40:#16 // run:()Ljava/lang/Runnable;
#28 = Utf8 Lambda
#29 = NameAndType #15:#16 // getRunnable:()Ljava/lang/Runnable;
#30 = Class #41 // java/lang/Runnable
#31 = NameAndType #40:#12 // run:()V
#32 = Class #42 // java/lang/System
#33 = NameAndType #43:#44 // out:Ljava/io/PrintStream;
#34 = Utf8 I am a Runnable!
#35 = Class #45 // java/io/PrintStream
#36 = NameAndType #46:#47 // println:(Ljava/lang/String;)V
#37 = Utf8 java/lang/Object
#38 = Methodref #48.#49 // java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
#39 = Methodref #3.#50 // Lambda.lambda$getRunnable$0:()V
#40 = Utf8 run
#41 = Utf8 java/lang/Runnable
#42 = Utf8 java/lang/System
#43 = Utf8 out
#44 = Utf8 Ljava/io/PrintStream;
#45 = Utf8 java/io/PrintStream
#46 = Utf8 println
#47 = Utf8 (Ljava/lang/String;)V
#48 = Class #51 // java/lang/invoke/LambdaMetafactory
#49 = NameAndType #52:#56 // metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
#50 = NameAndType #19:#12 // lambda$getRunnable$0:()V
#51 = Utf8 java/lang/invoke/LambdaMetafactory
#52 = Utf8 metafactory
#53 = Class #58 // java/lang/invoke/MethodHandles$Lookup
#54 = Utf8 Lookup
#55 = Utf8 InnerClasses
#56 = Utf8 (Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
#57 = Class #59 // java/lang/invoke/MethodHandles
#58 = Utf8 java/lang/invoke/MethodHandles$Lookup
#59 = Utf8 java/lang/invoke/MethodHandles
{
public Lambda();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 1: 0
public java.lang.Runnable getRunnable();
descriptor: ()Ljava/lang/Runnable;
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: invokedynamic #2, 0 // InvokeDynamic #0:run:()Ljava/lang/Runnable;
5: areturn
LineNumberTable:
line 3: 0
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=1, args_size=1
0: new #3 // class Lambda
3: dup
4: invokespecial #4 // Method "<init>":()V
7: invokevirtual #5 // Method getRunnable:()Ljava/lang/Runnable;
10: invokeinterface #6, 1 // InterfaceMethod java/lang/Runnable.run:()V
15: return
LineNumberTable:
line 7: 0
line 8: 15
}
SourceFile: "Lambda.java"
InnerClasses:
public static final #54= #53 of #57; //Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles
BootstrapMethods:
0: #24 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
Method arguments:
#25 ()V
#26 invokestatic Lambda.lambda$getRunnable$0:()V
#25 ()V
Анализируя
Что-нибудь привлекло ваше внимание? Та-та-та-дам.
Анонимный класс: 5: invokespecial #3 // Method AnonymousClass$1."<init>":(LAnonymousClass;)V
Лямбда: 0: invokedynamic #2, 0 // InvokeDynamic #0:run:()Ljava/lang/Runnable;
Кажется, анонимный класс был захвачен во время создания.
связь к его родительскому экземпляру: AnonymousClass$1."<init>":(LAnonymousClass;)V
и будет держаться за него до тех пор, пока всемогущий Сборщик Мусора™ не отметит его как недостижимый и не освободит от этого бремени.
Хотя эта ссылка не используется внутри компании, он такой анонимный жадный парень.
А если серьезно, здесь существует потенциальная утечка памяти, если вы передаете экземпляр анонимного класса внешнему миру.
С лямбда-выражениями это произойдет только в том случае, если вы явно или неявно ссылаетесь на этот в теле анонимной функции.
В противном случае, как в этом примере, лямбда не содержит ссылку на экземпляр, который ее вызывает .
Мы делаем это сами.
Предлагаю всем читателям провести эксперимент и посмотреть, что произойдет в каждом случае, если добавить в линию звонок.
.
нанизывать() в родительском экземпляре.
Как ударить человека в ногу? Я обещал тебе рассказать!
Самый простой способ справиться с потенциальной утечкой памяти — использовать нестатические методы внешнего класса внутри лямбды, если вас не особо интересует ее внутреннее состояние: public class LambdaCallsNonStatic {
public Runnable getRunnable() {
return () -> {
nonStaticMethod();
};
}
public void nonStaticMethod() {
System.out.println("I am a Runnable!");
}
public static void main(String[] args) {
new LambdaCallsNonStatic().
getRunnable().
run();
}
}
Лямбда получит ссылку на вызывающий ее экземпляр класса (правда, он будет создан один раз, но об этом ниже):
1: invokedynamic #2, 0 // InvokeDynamic #0:run:(LLambdaCallsNonStatic;).
Декомпиляция LambdaCallsNonStatic.class
Compiled from "LambdaCallsNonStatic.java"
public class LambdaCallsNonStatic {
public LambdaCallsNonStatic();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public java.lang.Runnable getRunnable();
Code:
0: aload_0
1: invokedynamic #2, 0 // InvokeDynamic #0:run:(LLambdaCallsNonStatic;)Ljava/lang/Runnable;
6: areturn
public void nonStaticMethod();
Code:
0: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #4 // String I am a Runnable!
5: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
public static void main(java.lang.String[]);
Code:
0: new #6 // class LambdaCallsNonStatic
3: dup
4: invokespecial #7 // Method "<init>":()V
7: invokevirtual #8 // Method getRunnable:()Ljava/lang/Runnable;
10: invokeinterface #9, 1 // InterfaceMethod java/lang/Runnable.run:()V
15: return
}
Решение: объявите используемый метод статическим или переместите его в отдельный служебный класс.
Вот и все?
Нет, есть еще одно замечательное преимущество лямбд-выражений по сравнению с анонимными классами.
Если вы когда-нибудь работали в застенках кровавого офиса предприятия и не дай бог вы написали что-то вроде этого: Collections.sort(list, new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return -Integer.compare(o1, o2);
}
});
Тогда к тебе подошел мудрейший руководитель отряда и сказал: Ты не экономен, Федор , вы тратите корпоративные ресурсы.
Давайте реорганизуем это как взрослый.
В конце концов, каждый раз при запуске этого фрагмента кода будет создаваться новый экземпляр компаратора.
В результате получилась вот такая портянка: public class CorporateComparators {
public static Comparator<Integer> integerReverseComparator() {
return IntegerReverseComparator.INSTANCE;
}
private enum IntegerReverseComparator implements Comparator<Integer> {
INSTANCE;
@Override
public int compare(Integer o1, Integer o2) {
return -Integer.compare(o1, o2);
}
}
}
.
Collections.sort(list, CorporateComparators.integerReverseComparator());
Стало удобнее, все теперь в своем файле и можно использовать повторно.
С последним согласен, но удобнее стало только если у тебя в голове вместо серого вещества DDR4. Читабельность такого кода не просто падает, а летит к чертям со сверхзвуковой скоростью.
С помощью лямбд можно держать логику ближе к месту непосредственного использования и не переплачивая за нее сверху: Collections.sort(list, (i1, i2) -> -Integer.compare(i1, i2));
Анонимная функция, которая не захватывает значения из внешнего контекста, будет облегченной и создаётся только один раз.
Хотя спецификация не обязывает конкретную реализацию виртуальной машины вести себя таким образом ( 15.27.4. Оценка лямбда-выражений во время выполнения ), но именно это и наблюдается в виртуальной машине Java HotSpot.
Java-версия
Ээксперименты проводились на: java version "1.8.0_92"
Java(TM) SE Runtime Environment (build 1.8.0_92-b14)
Java HotSpot(TM) 64-Bit Server VM (build 25.92-b14, mixed mode)
javac 1.8.0_92
javap 1.8.0_92
Окончательно
Статья не претендует на сверхстрогость, академичность и полноту, но мне кажется (я такая самонадеянная, сейчас получу первую цифру в комментариях) достаточно раскрывает две убойные особенности, которые заставляют становиться еще больше погружен в лямбды.Критика в комментариях, конструктивная или нет, абсолютно приветствуется.
Теги: #java #java 8 #lambdas #анонимные функции #замыкания #для начинающих #для начинающих #программирование #java #Функциональное программирование
-
Firefox 3 Ждет Инструкций Пользователя
19 Oct, 24