Java8ラムダと匿名クラス


111

Java8が最近リリースされ、その新しいラムダ式が本当にかっこいいように見えるので、これが私たちがこれまで使用していた匿名クラスの終焉を意味するのかと思っていました。

私はこれについて少し調べていて、Lambda式が体系的にそれらのクラスをどのように置き換えるかについていくつかのクールな例を見つけました。たとえば、コレクションのソートメソッドは、Comparatorの匿名インスタンスを取得してソートを実行するために使用していました。

Collections.sort(personList, new Comparator<Person>(){
  public int compare(Person p1, Person p2){
    return p1.firstName.compareTo(p2.firstName);
  }
});

今ラムダを使用して行うことができます:

Collections.sort(personList, (Person p1, Person p2) -> p1.firstName.compareTo(p2.firstName));

そして、驚くほど簡潔に見えます。だから私の質問は、ラムダの代わりにJava8でそれらのクラスを使い続ける理由はあるのでしょうか?

編集

同じ質問ですが、反対の方向で、匿名クラスの代わりにラムダを使用する利点は何ですか?ラムダは単一のメソッドインターフェイスでのみ使用できるので、この新しい機能はいくつかの場合にのみ使用されるショートカットだけですか、それとも本当に便利ですか?


5
もちろん、副作用のあるメソッドを提供するすべての匿名クラスについては。
tobias_k

11
:ちょうどあなたの情報のために、あなたはまた、コンパレータを構築することができComparator.comparing(Person::getFirstName)た場合、getFirstName()返却方法だろうfirstName
skiwi 2014年

1
または、複数のメソッドを持つ匿名クラス、または...
Mark Rotteveel

1
特にEDITの後に追加の質問があるため、近いものに投票する気になります。
Mark Rotteveel 14年

1
このトピックに関するすばらしい詳細記事:infoq.com/articles/Java-8-Lambdas-A-Peek-Under-the-Hood
Ram Patra

回答:


108

匿名内部クラス(AIC)を使用して、抽象クラスまたは具象クラスのサブクラスを作成できます。AICは、状態(フィールド)の追加を含む、インターフェースの具体的な実装も提供できます。AICのインスタンスは、thisそのメソッド本体でを使用して参照できるため、追加のメソッドを呼び出すことができ、その状態を時間とともに変化させることができます。これらのいずれもラムダには適用されません。

AICの使用の大部分は単一関数のステートレス実装を提供することであったため、ラムダ式に置き換えることができると思いますが、ラムダを使用できないAICの他の使用法があります。AICは留まります。

更新

AICとラムダ式のもう1つの違いは、AICが新しいスコープを導入することです。つまり、名前はAICのスーパークラスとインターフェースから解決され、字句的に囲まれた環境で発生する名前を隠すことができます。ラムダの場合、すべての名前が字句的に解決されます。


1
ラムダは状態を持つことができます。この点で、ラムダとAICの間に違いはありません。
nosid 2014年

1
@nosid AICは、任意のクラスのインスタンスと同様に、フィールドに状態を保持できます。この状態には、クラスの任意のメソッドからアクセスできます(そのため、変更できる可能性があります)。この状態は、オブジェクトがGCされるまで存在します。つまり、オブジェクトの範囲が無期限になるため、メソッド呼び出し間で永続化できます。ラムダの範囲が不明確な唯一の状態は、ラムダが検出されたときに取得されます。この状態は不変です。ラムダ内のローカル変数は変更可能ですが、ラムダ呼び出しの進行中にのみ存在します。
スチュアートマークス

1
@nosidああ、単一要素配列のハック。複数のスレッドからカウンターを使用しないでください。ヒープに何かを割り当ててラムダにキャプチャする場合は、AICを使用して、直接変更できるフィールドを追加することもできます。この方法でラムダを使用することはできますが、実際のオブジェクトを使用できるのはなぜでしょうか。
スチュアートマークス

2
AICはこのようなファイルを作成しますA$1.classが、Lambdaは作成しません。これをDifferenceに追加できますか?
Asif Mushtaq

2
@UnKnownこれは主に実装上の問題です。これは、AICとラムダの1つのプログラムがどのように影響するかには影響しません。ラムダ式はのような名前のクラスを生成することに注意してくださいLambdaClass$$Lambda$1/1078694789。ただし、このクラスは、ラムダメタファクトリではなく、オンザフライで生成されるjavacため、対応する.classファイルはありません。ただし、これも実装上の問題です。
スチュアートマークス

60

ラムダは優れた機能ですが、SAMタイプでのみ機能します。つまり、単一の抽象メソッドのみとインターフェースします。インターフェイスに複数の抽象メソッドが含まれるとすぐに失敗します。匿名クラスが役立つのは、この場合です。

したがって、匿名クラスを単に無視することはできません。そして参考までに、andのsort()型宣言をスキップすることで、メソッドをより簡単にすることができます:p1p2

Collections.sort(personList, (p1, p2) -> p1.firstName.compareTo(p2.firstName));

ここでメソッドリファレンスを使用することもできます。クラスにcompareByFirstName()メソッドを追加してPerson、以下を使用します。

Collections.sort(personList, Person::compareByFirstName);

または、のゲッターを追加してfirstName、直接Comparatorfrom Comparator.comparing()メソッドを取得します。

Collections.sort(personList, Comparator.comparing(Person::getFirstName));

4
私は知っていましたが、読みやすさの点では長い方を好みます。そうしないと、これらの変数の出所を見つけるのが混乱する可能性があるためです。
アミンアブタレブ2014年

@ AminAbu-Talebなぜ混乱するのでしょうか。これは有効なLambda構文です。型はとにかく推論されます。とにかく、それは個人的な選択です。タイプを明示的に指定できます。問題ない。
Rohit Jain 2014年

1
ラムダと匿名クラスの間に別の微妙な違いがあります:匿名クラスは直接newで注釈を付けることができますJavaの8種類の注釈などとして、new @MyTypeAnnotation SomeInterface(){};。これはラムダ式では不可能です。詳細については、こちらの質問を参照してください:ラムダ式の関数インターフェイスに注釈を付ける
Balder 2014年

36

匿名クラスによるラムダパフォーマンス

アプリケーションの起動時に、各クラスファイルをロードして検証する必要があります。

匿名クラスは、指定されたクラスまたはインターフェースの新しいサブタイプとしてコンパイラーによって処理されるため、それぞれに新しいクラスファイルが生成されます。

ラムダはバイトコード生成で異なり、より効率的で、JDK7に付属するinvokedynamic命令を使用します。

ラムダの場合、この命令を使用して、ラムダ式をバイトコードに変換してから実行時まで遅延させます。(命令は初めて呼び出されます)

その結果、ラムダ式は静的メソッドになります(実行時に作成されます)。(statelesとstatefullの場合には小さな違いがあります。これらは生成されたメソッド引数によって解決されます)


各ラムダにも新しいクラスが必要ですが、実行時に生成されるため、この意味でラムダは匿名クラスほど効率的ではありません。ラムダは、匿名クラスの新しいインスタンスを作成するために使用されるinvokedynamicよりも一般的に遅い経由でinvokespecial作成されます。したがって、この意味でラムダも低速です(ただし、JVM invokedynamicはほとんどの場合、呼び出しを最適化できます)。
ZhekaKozlov 2017年

3
@AndreiTomashpolskiy 1.礼儀正しくしてください。2.コンパイラエンジニアがこのコメントを読む:habrahabr.ru/post/313350/comments/#comment_9885460
ZhekaKozlov

@ ZhekaKozlov、JREソースコードを読み取ってjavap / debuggerを使用するためにコンパイラエンジニアである必要はありません。不足しているのは、ラムダメソッドのラッパークラスの生成が完全にメモリ内で行われ、コストがほとんどかからないことです。一方、AICのインスタンス化には、対応するクラスリソース(I / Oシステムコールを意味する)の解決と読み込みが含まれます。したがって、invokedynamicアドホッククラスの生成は、コンパイルされた匿名クラスに比べて非常に高速です。
Andrei Tomashpolskiy 2017年

@AndreiTomashpolskiy I / Oは必ずしも遅いわけではありません
ZhekaKozlov

14

以下の違いがあります。

1)構文

ラムダ式は、匿名内部クラス(AIC)と比べて見栄えが良い

public static void main(String[] args) {
    Runnable r = new Runnable() {
        @Override
        public void run() {
            System.out.println("in run");
        }
    };

    Thread t = new Thread(r);
    t.start(); 
}

//syntax of lambda expression 
public static void main(String[] args) {
    Runnable r = ()->{System.out.println("in run");};
    Thread t = new Thread(r);
    t.start();
}

2)適用範囲

匿名の内部クラスはクラスです。つまり、内部クラス内に定義された変数のスコープがあります。

一方、ラムダ式はそれ自体のスコープではなく、囲みスコープの一部です。

匿名の内部クラスとラムダ式の内部を使用する場合、同様のルールがsuperおよびthisキーワードに適用されます。匿名内部クラスの場合、このキーワードはローカルスコープを指し、superキーワードは匿名クラスのスーパークラスを指します。ラムダ式の場合、このキーワードは外側の型のオブジェクトを参照し、superは外側のクラスのスーパークラスを参照します。

//AIC
    public static void main(String[] args) {
        final int cnt = 0; 
        Runnable r = new Runnable() {
            @Override
            public void run() {
                int cnt = 5;    
                System.out.println("in run" + cnt);
            }
        };

        Thread t = new Thread(r);
        t.start();
    }

//Lambda
    public static void main(String[] args) {
        final int cnt = 0; 
        Runnable r = ()->{
            int cnt = 5; //compilation error
            System.out.println("in run"+cnt);};
        Thread t = new Thread(r);
        t.start();
    }

3)パフォーマンス

ラムダ式は純粋なコンパイル時のアクティビティであり、実行時に余分なコストが発生しない一方で、実行時に匿名の内部クラスはクラスの読み込み、メモリ割り当て、オブジェクトの初期化、非静的メソッドの呼び出しを必要とします。したがって、ラムダ式のパフォーマンスは、匿名の内部クラスに比べて優れています。**

**私はこの点が完全に真実ではないことを理解しています。詳細は下記質問をご参照ください。 ラムダと匿名の内部クラスのパフォーマンス:ClassLoaderの負荷を軽減しますか?


3

関数型プログラミングのために、Java 8のLambdaが導入されました。ボイラープレートコードを回避できる場所。ラムダに関する興味深い記事に出くわしました。

http://radar.oreilly.com/2014/04/whats-new-in-java-8-lambdas.html

単純なロジックにはラムダ関数を使用することをお勧めします。ラムダを使用して複雑なロジックを実装すると、問題が発生した場合にコードのデバッグにオーバーヘッドが発生します。


0

ラムダは単一の抽象メソッドを持つ関数に適しているため、匿名クラスが残りますが、他のすべてのケースでは匿名内部クラスがあなたの救世主です。


-1
  • ラムダ構文では、javaが推測できる明白なコードを記述する必要はありません。
  • を使用することによりinvoke dynamicラムダは匿名クラスに変換されません。コンパイル時ににません(Javaはオブジェクトの作成を行う必要がなく、メソッドのシグネチャに注意するだけで、オブジェクトを作成せずにメソッドにバインドできます)
  • ラムダは、実行する前に実行する必要があることよりも、実行したいことをより強調します
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.