Javaでラムダから脚を撃つ方法

そのような会話を聞くことができる場合があります。Java8で根本的な変更は発生しておらず、ラムダは古くからある匿名クラスであり、構文糖がsugarしみなく散りばめられています。 どんなに! 今日は、ラムダと匿名クラスの違いについて話すことをお勧めします。 そして、なぜ足を踏み入れるのが難しくなったのか。



すでに匿名機能を習得していると信じている人から時間をとらないために、簡単なタスクです。 以下の2つのコードスニペットの違いは何ですか?



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(); } }
      
      





そして2番目のフラグメント:



 public class Lambda { public Runnable getRunnable() { return () -> System.out.println("I am a Runnable!"); } public static void main(String[] args) { new Lambda().getRunnable().run(); } }
      
      





すぐに回答できる場合は、さらに読みたいかどうかを自分で決めてください。



逆コンパイル



両方のオプションのバイトコードを確認します。 ( -verboseフラグを使用した詳細な逆コンパイルは、ネタバレです。)



匿名クラスで



 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
      
      





強力なガベージコレクター™が到達不能とマークし、この負担から解放するまで保持します。 このリンクは内部では使用されませんが、このような匿名の欲張りです。



しかし、真剣に、匿名クラスのインスタンスを外部に提供すると、潜在的なメモリリークが発生します。 ラムダを使用すると、匿名関数の本体で明示的または暗黙的にこれを参照する場合にのみ発生します。 それ以外の場合、この例のように、 ラムダはそれを呼び出すインスタンスへの参照を保持しません



自分でやってください。 すべての読者に実験を行って、 .toString()呼び出しを生成インスタンスへの行を行に追加した場合の各ケースで何が起こるかを見てもらいます。



足を踏み入れるには? 彼は言うと約束した!



潜在的なメモリリークに遭遇する最も簡単な方法は、ラムダ内の外部クラスの非静的メソッドを使用することです。これは、内部状態に本当に関心がない場合です。



 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。Lambda式の実行時評価 )、Java HotSpot VMではこれがまさに観察されます。



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
      
      





結論として



この記事は、非常に厳密で、学術的で、完全なふりをしていませんが、私には思えます(私は、慢です、コメントの最初の数字を取得します)、ラムダをさらに浸透させる2つのキラー機能を十分に明らかにしています。 コメントでの批判は、建設的であり、非常に断固として歓迎されません。



All Articles