高度な Java -- ラムダ式

ラムダ式

匿名クラス ( anonymous classes) に関する 1 つの問題は、メソッドを 1 つだけ含むインターフェイスなど、匿名クラスの実装が非常に単純な場合、匿名クラスの構文が扱いにくく、不明瞭に見える可能性があることです。このような場合、通常は、誰かがボタンをクリックしたときにどのようなアクションを実行するかなど、機能を引数として別のメソッドに渡そうとします。ラムダ式を使用すると、機能をメソッドのパラメーターとして扱うか、コードをデータとして扱うことで、まさにそれが可能になります。

前のセクション「匿名クラスでは、基本クラスに名前を付けずに匿名クラスを実装する方法を示しました。通常、これはクラスに名前を付けるよりも簡潔ですが、メソッドが 1 つしかないクラスの場合、匿名クラスであっても少し過剰で面倒です。ラムダ式を使用すると、単一メソッド クラスのインスタンスをよりコンパクトに表現できます。

このセクションでは次のトピックについて説明します。

1. ラムダ式の理想的な使用例

機能インターフェイスの概要:

Consumer<T>

// 表示接受单个输入参数且不返回结果的操作。与大多数其他功能接口不同,Consumer 期望通过副作用来操作。
// 这是一个函数式接口,其函数式方法是accept(Object)。

@FunctionalInterface
public interface Consumer<T> {
    
    
	// 对给定参数执行此操作。
	void accept(T t);
	
	// 返回一个组合的Consumer,该Consumer按顺序执行此操作,然后执行after操作。
	// 如果执行任何操作都会抛出异常,则将其传递给组合操作的调用者。如果执行此操作会引发异常,
	// 则不会执行after操作。
	default Consumer<T> andThen(Consumer<? super T> after) {
    
    }
}

IntConsumer

単一の整数パラメータを受け取り、結果を返さない操作を表しますこれはConsumer のint基本的な型の特殊化です。他のほとんどの機能インターフェイスとは異なり、IntConsumer副作用を介して動作することが期待されます。

public interface IntConsumer {
    
    
	// 对给定参数执行此操作。
	void accept(int value);
}

Function<T, R>

// 表示接受一个参数并产生结果的函数。

@FunctionalInterface
public interface Function<T, R> {
    
    
	// 将此函数应用于给定参数。
	R apply(T t);
	
	// 返回一个组合函数,该函数首先将before函数应用于其输入,然后将此Function应用于结果。
	// 如果对任何一个函数的求值引发异常,则将其传递给组合函数的调用者。
	default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
    
    }
		
	// 返回一个组合函数,该函数首先将此函数应用于其输入,然后将after函数应用于结果。
	// 如果对任何一个函数的求值引发异常,则将其传递给组合函数的调用者。
	default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
    
    }
	
	// 返回一个总是返回其输入参数的function
	static <T> Function<T, T> identity() {
    
    }
}

BiFunction<T, U, R>

2 つの引数を受け取り、結果を生成する関数を表します。これは関数のバイナリ特殊化です。

これは、関数メソッドが である関数インターフェイスですapply(Object, Object)


public interface BiFunction<T, U, R> {
    
    
	// 将此函数应用于给定的参数。
	// t – the first function argument
	// u – the second function argument
	// Returns: the function result
	R apply(T t, U u);
}

BinaryOperator<T>

同じ型の 2 つのオペランドに対する演算により、オペランドと同じ型の結果が生成されることを示します。BiFunctioこれは、オペランドと結果の両方が同じ型である場合の n の特殊化です。

public interface BinaryOperator<T> extends BiFunction<T,T,T> {
    
    
}

Comparator<T>

オブジェクトのコレクションに全体的な順序を課す比較関数。コンパレータを並べ替えメソッド ( または などCollections.sort )に渡すとArrays.sort、並べ替え順序を正確に制御できます。sorted setsコンパレータは、特定のデータ構造 ( やなど)の順序を制御し sorted mapsたり、自然な順序を持たないオブジェクトのコレクションに順序を付けるために使用することもできます。
比較器によってcセットに課せられる順序付けは、その中合計がおよび と同じブール値を持つS場合に限り、一貫性があると言われます。c.compare(e1, e2)==0 Se1e2e1.equals(e2)equals

public interface Comparator<T> {
    
    
	// 比较它的两个参数的顺序。当第一个参数小于、等于或大于第
	// 二个参数时,返回负整数、零或正整数。
	// 在前面的描述中,符号`sgn(expression)`表示数学上的signum函数,
	// 它被定义为根据表达式的值是负、零还是正返回-1、0或1中的一个。
	int compare(T o1, T o2);
}

Supplier<T>

結果のプロバイダーを示します。
サプライヤーが呼び出されるたびに、新しい結果または異なる結果を返す必要はありません。
これは関数型インターフェイスであり、その関数型メソッドは ですget()

public interface Supplier<T> {
    
    

    /**
     * Gets a result.
     *
     * @return a result
     */
    T get();
}

ToIntFunction<T>

int値の結果を生成する関数を表します。これがFunction結果として得られるint原始的な特殊化です。

これは関数型インターフェイスであり、その関数型メソッドは ですapplyAsInt(Object)

@FunctionalInterface
public interface ToIntFunction<T> {
    
    

    /**
     * Applies this function to the given argument.
     *
     * @param value the function argument
     * @return the function result
     */
    int applyAsInt(T value);
}

ソーシャル ネットワーキング アプリケーションを作成しているとします。特定の基準を満たすソーシャル ネットワーキング アプリケーションのメンバーに対して、管理者がメッセージの送信などのあらゆる種類のアクションを実行できる機能を作成したいと考えています。次の表に、この使用例の詳細を示します。

ここに画像の説明を挿入
このソーシャル ネットワーキング アプリケーションのメンバーは、次のPersonクラスで表されると想定します。

public class Person {
    
    

    public enum Sex {
    
    
        MALE, FEMALE
    }

    String name;
    LocalDate birthday;
    Sex gender;
    String emailAddress;

    public int getAge() {
    
    
        // ...
    }

    public void printPerson() {
    
    
        // ...
    }
}

ソーシャル ネットワーキング アプリケーションのList<Person>インスタンスにメンバーが保存されているとします。

このセクションでは、このユースケースに対する簡単なアプローチから始めます。これは、ローカルおよび匿名クラスを使用してこのアプローチを改良し、ラムダ式を使用した効率的で簡潔なアプローチを提供します。このセクションで説明されているコードの抜粋をRosterTestの例で見つけます。

方法 1: 特性に一致するメンバーを検索するメソッドを作成する

シンプルなアプローチは、性別や年齢などの特性に一致するメンバーを検索する複数のメソッドを作成することです。次のメソッドは、指定された年齢以上のメンバーを出力します。

public static void printPersonsOlderThan(List<Person> roster, int age) {
    
    
    for (Person p : roster) {
    
    
        if (p.getAge() >= age) {
    
    
            p.printPerson();
        }
    }
}

注: List は順序付けされたCollectionですコレクションは、複数の要素を 1 つのユニットに結合したオブジェクトです。コレクションは、集約されたデータの保存、取得、操作、通信に使用されます。コレクションの詳細については、「コレクションの追跡」を参照してください。

このアプローチでは、アプリケーションが脆弱になる可能性があります。つまり、新しいデータ型などの更新の導入により、アプリケーションが適切に動作しなくなる可能性があります。アプリケーションをアップグレードし、Person別のメンバー変数が含まれるようにクラスの構造を変更するとします。おそらく、クラスは別のデータ型またはアルゴリズムを使用して年齢を記録および測定します。この変更に対応するには、多くの API を書き直す必要があります。また、このアプローチは不必要に制限的です。特定の年齢より若いメンバーを印刷したい場合はどうなるでしょうか。

アプローチ 2: より一般的な検索方法を作成する

次のメソッドはprintPersonsOlderThanよりも一般的で、指定された年齢範囲内のメンバーを出力します。

public static void printPersonsWithinAgeRange(
    List<Person> roster, int low, int high) {
    
    
    for (Person p : roster) {
    
    
        if (low <= p.getAge() && p.getAge() < high) {
    
    
            p.printPerson();
        }
    }
}

指定した性別のメンバー、または指定した性別と年齢範囲の組み合わせを印刷したい場合はどうなりますか?Personクラスを変更して、関係ステータスや地理的位置などの属性を追加することにした場合はどうなりますか? このアプローチはよりprintPersonsOlderThan一般的ですが、しかし、考えられる検索クエリごとに個別のメソッドを作成しようとすると、コードが脆弱になってしまいます。代わりに、検索基準を指定するコードを異なるクラスに分離できます。

方法 3: 部分クラスに検索条件コードを指定する

次のメソッドは、指定した検索基準に一致するメンバーを出力します。

public static void printPersons(
    List<Person> roster, CheckPerson tester) {
    
    
    for (Person p : roster) {
    
    
        if (tester.test(p)) {
    
    
            p.printPerson();
        }
    }
}

このメソッドは、メソッドtester.testを呼び出すことによってList、パラメータ リストに含まれる各インスタンスがパラメータで指定された検索条件を満たすPersonかどうかを確認します。メソッドテスターの場合。test は を返しインスタンス上でメソッドを呼び出します。CheckPersontestertrue PersonprintPersons

CheckPerson検索基準を指定するには、次のインターフェイスを実装できます。

interface CheckPerson {
    
    
    boolean test(Person p);
}

次のクラスは、メソッドの実装を指定することによってインターフェイスtestを実装します。CheckPersonこのメソッドは、米国での選択的サービスを受ける資格のあるメンバーをフィルターします。Personパラメータが男性で、年齢が 18 ~ 25 歳の場合、次の値が返されますtrue

class CheckPersonEligibleForSelectiveService implements CheckPerson {
    
    
    public boolean test(Person p) {
    
    
        return p.gender == Person.Sex.MALE &&
            p.getAge() >= 18 &&
            p.getAge() <= 25;
    }
}

このクラスを使用するには、その新しいインスタンスを作成し、printPersonsメソッドを呼び出す必要があります。

printPersons(
    roster, new CheckPersonEligibleForSelectiveService());

このアプローチは脆弱ではありませんが (Person構造を変更してもメソッドをオーバーライドする必要はありません)、それでも追加のコードが必要です。アプリケーションで実行する予定のすべての検索には、新しいインターフェイスとローカル クラスが必要です。インターフェイスが実装されているためCheckPersonEligibleForSelectiveService、ローカル クラスの代わりに匿名クラスを使用でき、検索のたびに新しいクラスを宣言する必要がありません。

方法 4: 匿名クラスで検索条件コードを指定する

以下のメソッド呼び出しの引数の 1printPersonsつは、米国での選択的奉仕の資格のあるメンバー (18 歳から 25 歳までの男性メンバー) をフィルタリングするために使用される匿名クラスです。

printPersons(
    roster,
    new CheckPerson() {
    
    
        public boolean test(Person p) {
    
    
            return p.getGender() == Person.Sex.MALE
                && p.getAge() >= 18
                && p.getAge() <= 25;
        }
    }
);

このアプローチでは、実行する検索ごとに新しいクラスを作成する必要がないため、必要なコードの量が削減されます。CheckPersonただし、インターフェイスにメソッドが 1 つしか含まれていないことを考慮すると、匿名クラスの構文は非常に大きくなります。この場合、次のセクションで説明するように、匿名クラスの代わりに Lambda 式を使用できます。

方法 5: ラムダ式を使用して検索条件コードを指定する

CheckPersonインターフェイスは機能インターフェイス ( functional interface) です。関数型インターフェイスは、抽象メソッドを1 つだけ含むインターフェイスです(関数型インターフェイスには 1 つ以上のデフォルト メソッドまたは静的メソッドが含まれる場合があります。)関数型インターフェイスには抽象メソッドが 1 つだけ含まれるため、実装時にメソッド名を省略できますこれを行うには、匿名クラス式の代わりにラムダ式を使用できます。これは、次のメソッド呼び出しで強調表示されています。

printPersons(
    roster,
    (Person p) -> p.getGender() == Person.Sex.MALE
        && p.getAge() >= 18
        && p.getAge() <= 25
);

ラムダ式を定義する方法については、「ラムダ式の構文」を参照してください。

CheckPersonインターフェイスの代わりに標準の関数インターフェイスを使用すると、必要なコードの量がさらに削減されます。

方法 6: ラムダ式を使用した標準関数インターフェイス

CheckPersonインターフェースを再考してください。

interface CheckPerson {
    
    
    boolean test(Person p);
}

これは非常にシンプルなインターフェースです。これは、抽象メソッドを 1 つだけ含むため、関数型インターフェイスですこのメソッドは 1 つのパラメータを受け入れ、ブール値を返します。このメソッドは非常に単純なので、アプリケーションで定義する価値はおそらくありません。したがって、JDK では、java.util.functionパッケージに含まれるいくつかの標準関数インターフェイスが定義されています。

たとえば、Predicate<T>代わりにインターフェイスを使用できますCheckPersonこのインターフェイスには次のものが含まれますboolean test(T t)

interface Predicate<T> {
    
    
    boolean test(T t);
}

インターフェースはPredicate<T>汎用インターフェースの一例です。(ジェネリックスの詳細については、「ジェネリックス (更新)」<>レッスンを参照してください。) ジェネリック インターフェイスなどのジェネリック型では、山かっこ ( ) 内に 1 つ以上の型パラメーターを指定します。インターフェイスには型パラメータが 1 つだけ含まれますT実際の型パラメーターを使用してジェネリック型を宣言またはインスタンス化すると、パラメーター化された型が得られます。たとえば、パラメータ化された型はPredicate<Person>次のようになります。

interface Predicate<Person> {
    
    
    boolean test(Person t);
}

このパラメータ化された型には、 CheckPerson.boolean test(Person p)同じ戻り値の型とパラメータを持つメソッドが含まれています。Predicate<T>したがって、次のように代わりに を使用できますCheckPerson

public static void printPersonsWithPredicate(
    List<Person> roster, Predicate<Person> tester) {
    
    
    for (Person p : roster) {
    
    
        if (tester.test(p)) {
    
    
            p.printPerson();
        }
    }
}

したがって、以下のメソッド呼び出しは、方法 3: ローカル クラスで検索条件コードを指定して、選挙サービスに適格なメンバーを取得する方法と同じですprintPersons

printPersonsWithPredicate(
    roster,
    p -> p.getGender() == Person.Sex.MALE
        && p.getAge() >= 18
        && p.getAge() <= 25
);

メソッド内でラムダ式が可能な場所はこれだけではありません。以下のメソッドは、ラムダ式を使用する他の方法を提案しています

方法 7: アプリケーション全体でラムダ式を使用する

printPersonsWithPredicateメソッドを再考し、ラムダ式が他にどこで使用できるかを確認してください。

public static void printPersonsWithPredicate(
    List<Person> roster, Predicate<Person> tester) {
    
    
    for (Person p : roster) {
    
    
        if (tester.test(p)) {
    
    
            p.printPerson();
        }
    }
}

このメソッドは、Listパラメータ ロスターに含まれる各インスタンスがパラメータで指定された基準をPerson満たしていることを確認します。インスタンスがテスターに​​よって指定された基準を満たしている場合、インスタンス上でメソッドが呼び出されますPredicatetesterPersonPersonprintPerson

printPersonメソッドを呼び出す代わりに、Personテスターが指定した基準を満たすインスタンスに対して実行する別のアクションを指定できます。このアクションは、Lambda 式を使用して指定できます。Personたとえば、1 つの引数 (型のオブジェクト) を受け取り、それを返すprintperson のようなラムダ式が必要だとしますvoidラムダ式を使用するには、関数インターフェイスを実装する必要があることに注意してください。Personこの場合、型の 1 つのパラメーターを受け取り、それを返す抽象メソッドを含む関数型インターフェイスが必要ですvoidConsumer<T>インターフェイスにはvoid accept(T t)、次の特性を持つメソッドが含まれています。次のメソッドは、p.printPerson()呼び出しをConsumer<Person>インスタンスのacceptメソッドの呼び出しに置き換えます。

public static void processPersons(
    List<Person> roster,
    Predicate<Person> tester,
    Consumer<Person> block) {
    
    
        for (Person p : roster) {
    
    
            if (tester.test(p)) {
    
    
                block.accept(p);
            }
        }
}

したがって、以下のメソッド呼び出しは、「方法 3:選挙サービスの資格のあるメンバーのローカル クラスで検索条件コードを指定する」と同じですprintPersonsメンバーの出力に使用されるラムダ式が強調表示されます。

processPersons(
     roster,
     p -> p.getGender() == Person.Sex.MALE
         && p.getAge() >= 18
         && p.getAge() <= 25,
     p -> p.printPerson()
);

メンバーのプロフィールを印刷するだけでなく、さらに操作したい場合はどうすればよいでしょうか? メンバーのプロフィールを検証したり、連絡先情報を取得したりしたいとしますか? その場合、戻り値を含む抽象化が必要になります。方法。Function<T,R> インターフェースにはメソッドが含まれますR apply(T t)次のメソッドは、mapperパラメータで指定されたデータを取得し、blockパラメータで指定されたデータに対して操作を実行します。

public static void processPersonsWithFunction(
    List<Person> roster,
    Predicate<Person> tester,
    Function<Person, String> mapper,
    Consumer<String> block) {
    
    
    for (Person p : roster) {
    
    
        if (tester.test(p)) {
    
    
            String data = mapper.apply(p);
            block.accept(data);
        }
    }
}

次のメソッドは、選択サービスの対象となる名簿の各メンバーから電子メール アドレスを取得し、それを出力します。

processPersonsWithFunction(
    roster,
    p -> p.getGender() == Person.Sex.MALE
        && p.getAge() >= 18
        && p.getAge() <= 25,
    p -> p.getEmailAddress(),
    email -> System.out.println(email)
);

方法 8: ジェネリックをより広範囲に使用する

processPersonsWithFunctionアプローチを再考してください。以下は、任意のデータ型の要素を含むコレクションを引数として受け入れるその汎用バージョンです。

public static <X, Y> void processElements(
    Iterable<X> source,
    Predicate<X> tester,
    Function <X, Y> mapper,
    Consumer<Y> block) {
    
    
    for (X p : source) {
    
    
        if (tester.test(p)) {
    
    
            Y data = mapper.apply(p);
            block.accept(data);
        }
    }
}

投票サービスに参加する資格のあるメンバーの電子メール アドレスを一覧表示するには、次のようにメソッドを呼び出しますprocessElements

processElements(
    roster,
    p -> p.getGender() == Person.Sex.MALE
        && p.getAge() >= 18
        && p.getAge() <= 25,
    p -> p.getEmailAddress(),
    email -> System.out.println(email)
);

このメソッド呼び出しは次のことを行います。

  1. コレクションからオブジェクト ソースを取得しますsourceこの場合、Personオブジェクトのソースをコレクション名簿から取得します。コレクション ロスターはList型のコレクション、およびIterable型のオブジェクトであることに注意してください。
  2. Predicate オブジェクトtesterに一致するオブジェクトをフィルタリングします。この場合、Predicateオブジェクトは、どのメンバーが選挙サービスに参加する資格があるかを指定するラムダ式です。
  3. 各フィルター オブジェクトを、Functionオブジェクトmapperによって指定された値にマップします。この場合、Functionオブジェクトはlambdaメンバーの電子メール アドレスを返す式です。
  4. マップされた各オブジェクトに対して、Consumer オブジェクトblockによって指定された 1 つの操作を実行します。この場合、Consumerオブジェクトはlambda文字列、つまりFunctionオブジェクトが返す電子メール アドレスを出力する式です。

これらの各操作を集計操作に置き換えることができます。

方法 9: ラムダ式をパラメータとして受け入れる集計演算を使用する

次の例では、集計操作を使用して、roster 選挙の対象となるコレクションのメンバーの電子メール アドレスを出力します。

roster
    .stream()
    .filter(
        p -> p.getGender() == Person.Sex.MALE
            && p.getAge() >= 18
            && p.getAge() <= 25)
    .map(p -> p.getEmailAddress())
    .forEach(email -> System.out.println(email));

次の表は、proceselementメソッドによって実行される各操作と、対応する集計操作、
ここに画像の説明を挿入
Operation filtermapおよびforEachisAggregateOperation( aggregate operations) をマップします。集計操作は、streamコレクションから直接ではなく、ストリーム ( ) の要素に対して機能します (この例で最初に呼び出されるメソッドが であるのはそのためですstream)。ストリーム ( stream) は要素のシーケンスです。セットとは異なり、要素を格納するデータ構造ではありません。代わりに、ストリームはパイプを介してコレクションなどのソースから値を運びます。パイプライン ( pipeline) は、この場合、一連のストリーム操作ですfilter- map-forEachさらに、集計操作では多くの場合Lambda、式を引数として受け入れるため、式の動作をカスタマイズできます。

集計操作の詳細については、「集計操作に関するレッスン」を参照してください。

1.1 GUI アプリケーションのラムダ式

グラフィカル ユーザー インターフェイス (GUI) アプリケーションでキーの押下、マウス操作、スクロール操作などのイベントを処理するには、通常、イベント ハンドラーを作成する必要があります。イベント ハンドラーには、通常、特定のインターフェイスの実装が含まれます。通常、イベント ハンドラー インターフェイスは関数型インターフェイスであり、メソッドが 1 つしかないことがよくあります。

JavaFX サンプルHelloWorld.java (匿名クラスに関する前のセクションで説明) では、次のステートメントで強調表示された匿名クラスを Lambda 式に置き換えることができます。

		btn.setOnAction(new EventHandler<ActionEvent>() {
    
    

            @Override
            public void handle(ActionEvent event) {
    
    
                System.out.println("Hello World!");
            }
        });

メソッド呼び出しは、オブジェクトbtn.setOnActionによって表されるボタンを選択したbtnときに何が起こるかを指定します。このメソッドは、EventHandler<ActionEvent>次の型のオブジェクトを受け取ります。EventHandler<ActionEvent>インターフェイスには 1 つのメソッドのみが含まれますvoid handle(T event)このインターフェイスは関数インターフェイスであるため、以下で強調表示されているようにラムダ式に置き換えることができます。

 btn.setOnAction(
          event -> System.out.println("Hello World!")
        );

1.2 ラムダ式の構文

Lambda式は次のもので構成されます。

1. かっこで囲まれた仮パラメータのカンマ区切りのリスト。

CheckPerson.testこのメソッドには、クラスのインスタンスpを表す1 つのパラメータが含まれています。注:ラムダ式のパラメータのデータ型は省略できますまた、パラメータが 1 つだけの場合は、括弧を省略できますたとえば、次のラムダ式も有効です。Person

p -> p.getGender() == Person.Sex.MALE 
    && p.getAge() >= 18
    && p.getAge() <= 25

2. 矢印マーク->

3. 本文。単一の式またはステートメントのブロックで構成されます。

この例では次の式を使用します。

p.getGender() == Person.Sex.MALE 
    && p.getAge() >= 18
    && p.getAge() <= 25

単一の式が指定されている場合、Java ランタイムはその式を評価し、その値を返します。あるいは、次のreturnステートメントを使用することもできます。

p -> {
    
    
    return p.getGender() == Person.Sex.MALE
        && p.getAge() >= 18
        && p.getAge() <= 25;
}

returnステートメントは式ではありません。ラムダ式では、ステートメントを{}中括弧 ( ) で囲む必要があります。ただし、void メソッド呼び出しを中括弧で囲む必要はありません。たとえば、次は有効なラムダ式です。

email -> System.out.println(email)

Lambda式はメソッド宣言によく似ていることに注意してください。Lambda式は匿名メソッド、つまり名前のないメソッドと考えることができます。

次の例は、Calculator複数の仮パラメータを含むラムダ式の例です。

public class Calculator {
    
    
  
    interface IntegerMath {
    
    
        int operation(int a, int b);   
    }
  
    public int operateBinary(int a, int b, IntegerMath op) {
    
    
        return op.operation(a, b);
    }
 
    public static void main(String... args) {
    
    
    
        Calculator myApp = new Calculator();
        IntegerMath addition = (a, b) -> a + b;
        IntegerMath subtraction = (a, b) -> a - b;
        System.out.println("40 + 2 = " +
            myApp.operateBinary(40, 2, addition));
        System.out.println("20 - 10 = " +
            myApp.operateBinary(20, 10, subtraction));    
    }
}

operateBinaryメソッドは 2 つの整数オペランドに対して数学演算を実行します。操作自体は のIntegerMathインスタンスによって指定されます。この例では、式と をlambda使用して 2 つの操作を定義しますこの例では次のように出力されます。additionsubtraction

40 + 2 = 42
20 - 10 = 10

1.3 外側のスコープ内のローカル変数へのアクセス

ローカルクラスや匿名クラスと同様に、ラムダ式は変数をキャプチャできます。変数は、それを囲んでいるスコープのローカル変数と同じアクセス権を持ちます。ただし、ローカル クラスや匿名クラスとは異なり、ラムダ式にはシャドウイングの問題がありません (詳細については、 「シャドウイング」を参照してください)。ラムダ式には字句スコープがあります。これは、スーパータイプから名前を継承せず、新しいレベルのスコープを導入しないことを意味しますラムダ式の宣言は、それを囲んでいる環境での宣言と同じ方法で解釈されます。次のLambdaScopeTest の例は、これを示しています。

import java.util.function.Consumer;
 
public class LambdaScopeTest {
    
    
 
    public int x = 0;
 
    class FirstLevel {
    
    
 
        public int x = 1;
        
        void methodInFirstLevel(int x) {
    
    

            int z = 2;
             
            Consumer<Integer> myConsumer = (y) -> 
            {
    
    
                // The following statement causes the compiler to generate
                // the error "Local variable z defined in an enclosing scope
                // must be final or effectively final" 
                //
                // z = 99;
                
                System.out.println("x = " + x); 
                System.out.println("y = " + y);
                System.out.println("z = " + z);
                System.out.println("this.x = " + this.x);
                System.out.println("LambdaScopeTest.this.x = " +
                    LambdaScopeTest.this.x);
            };
 
            myConsumer.accept(x);
 
        }
    }
 
    public static void main(String... args) {
    
    
        LambdaScopeTest st = new LambdaScopeTest();
        LambdaScopeTest.FirstLevel fl = st.new FirstLevel();
        fl.methodInFirstLevel(23);
    }
}

この例では、次の出力が生成されます。

ここに画像の説明を挿入
myConsumerラムダ式の宣言でパラメータxを置き換えるy、コンパイラはエラーを生成します。

Consumer<Integer> myConsumer = (x) -> {
    
    
    // ...
}

Lambda表达式的参数x不能重新声明在封闭作用域中定义的另一个局部变量ラムダ式では新しいレベルのスコープが導入されないため、コンパイラはエラー " " を生成します。したがって、外側のスコープのフィールド、メソッド、およびローカル変数に直接アクセスできます。たとえば、ラムダ式はmethodInFirstLevelメソッドのパラメータに直接アクセスしますx囲んでいるクラス内の変数にアクセスするには、キーワードを使用しますthisこの例では、 this はxメンバー変数 を指しますFirstLevel.x

ただし、ローカル クラスや匿名クラスと同様に、ラムダ式は、最終または事実上最終のローカル変数と、それを囲んでいるブロックのパラメータにのみアクセスできますこの例では、変数 z は実際には Final であり、一度初期化されると、その値は決して変わりません。ただし、myConsumer次の代入ステートメントをラムダ式に追加するとします。

Consumer<Integer> myConsumer = (y) -> {
    
    
    z = 99;
    // ...
}

この代入ステートメントにより、変数 z は事実上最終的なものではなくなります。その結果、Java コンパイラは、「囲んでいるスコープで定義されたローカル変数 z は、final または実質的にfinal である必要があります」のようなエラー メッセージを生成します。

1.4 ターゲットの種類

ラムダ式のタイプはどのように決定しますか? 18 歳から 25 歳までの男性メンバーを選択するラムダ式を思い出してください。

p -> p.getGender() == Person.Sex.MALE
    && p.getAge() >= 18
    && p.getAge() <= 25

このラムダ式は、次の 2 つのメソッドで使用されます。

  • public static void printPersons(List<Person> roster, CheckPerson tester)(方法3:部分クラスに検索条件コードを指定する)
  • public void printPersonsWithPredicate(List<Person> roster, Predicate<Person> tester)(アプローチ 6: 標準関数インターフェイスとラムダ式を使用する)

Java ランタイムはprintPersonsメソッドを呼び出すときに のデータ型を予期するCheckPersonため、ラムダ式はその型になります。ただし、Java ランタイムがprintPersonsWithPredicateメソッドを呼び出すときは、データ型が であることが予期されるPredicate<Person>ため、ラムダ式はその型になります。これらのメソッドで予期されるデータ型は、ターゲット型( target type) と呼ばれます。ラムダ式の型を決定するために、Java コンパイラは、ラムダ式が出現するコンテキストまたはケースのオブジェクト型を使用します次に、Java コンパイラがターゲットの型を判断できる場合にのみ、ラムダ式を使用できます。

  • 変数の宣言

  • 課題

  • ステートメントを返す

  • 配列初期化子

  • メソッドまたはコンストラクターの引数

  • ラムダ式の本体

  • 条件式、?:

  • キャスト式

ターゲットのタイプとメソッドのパラメータ

メソッド パラメーターの場合、Java コンパイラーは他の 2 つの言語機能 (オーバーロード解決型パラメーター推論)を使用してターゲットの型を決定します。

次の 2 つの関数インターフェイス ( java.lang.Runnableおよびjava.util.concurrent.Callable)について考えてみましょう。

public interface Runnable {
    
    
    void run();
}

public interface Callable<V> {
    
    
    V call();
}

メソッドはRunnable.run値を返さないがCallable<V>.call

次のようにメソッドをオーバーロードしたとしますinvoke (メソッドのオーバーロードの詳細については、 「メソッドの定義」を参照してください)。

void invoke(Runnable r) {
    
    
    r.run();
}

<T> T invoke(Callable<T> c) {
    
    
    return c.call();
}

次のステートメントではどのメソッドが呼び出されますか?

String s = invoke(() -> "done");

メソッドはinvoke(Callable<T>)値を返すために呼び出されますが、メソッドは値invoke(Runnable)を返しません。この場合、()-> "done"ラムダ式の型は ですCallable<T>

1.5 シリアル化

ラムダ式は、そのターゲット型とそれがキャプチャするパラメータがシリアル化可能な場合にシリアル化できますただし、内部クラスと同様に、ラムダ式のシリアル化は強く推奨されません

2. メソッドのリファレンス

ラムダ式を使用して匿名メソッドを作成できますただし、ラムダ式が既存のメソッドを呼び出すだけである場合があります。このような場合、多くの場合、既存のメソッドを名前で参照する方が明確です。メソッド参照を使用すると、これを行うことができます。メソッド参照は、すでに名前が付いているメソッドに対する簡潔で読みやすいラムダ式です。

ラムダ式のセクションで説明したPersonクラスをもう一度考えてみましょう

public class Person {
    
    

    // ...
    
    LocalDate birthday;
    
    public int getAge() {
    
    
        // ...
    }
    
    public LocalDate getBirthday() {
    
    
        return birthday;
    }   

    public static int compareByAge(Person a, Person b) {
    
    
        return a.birthday.compareTo(b.birthday);
    }
    
    // ...
}

ソーシャル ネットワーキング アプリケーションに配列に含まれるメンバーがあり、その配列を年齢で並べ替えたいとします。次のコードを使用できます (このセクションで説明されているコードの抜粋は、MethodReferencesTest の例にあります)。

Person[] rosterAsArray = roster.toArray(new Person[roster.size()]);

class PersonAgeComparator implements Comparator<Person> {
    
    
    public int compare(Person a, Person b) {
    
    
        return a.getBirthday().compareTo(b.getBirthday());
    }
}
        
Arrays.sort(rosterAsArray, new PersonAgeComparator());

sort呼び出しのメソッド シグネチャは次のとおりです。

static <T> void sort(T[] a, Comparator<? super T> c)

このインターフェイスはComparator機能インターフェイスであることに注意してください。Comparatorしたがって、実装クラスの新しいインスタンスを定義して作成する代わりに、ラムダ式を使用できます。

Arrays.sort(rosterAsArray,
    (Person a, Person b) -> {
    
    
        return a.getBirthday().compareTo(b.getBirthday());
    }
);

ただし、2 つの Person インスタンスの生年月日を比較する方法はすでにPerson.compareByAge存在します。このメソッドはラムダ式の本体で呼び出すことができます。

Arrays.sort(rosterAsArray,
    (a, b) -> Person.compareByAge(a, b)
);

このラムダ式は既存のメソッドを呼び出すため、ラムダ式の代わりにメソッド参照を使用できます

Arrays.sort(rosterAsArray, Person::compareByAge);

メソッド参照はPerson::compareByAge意味的には(a, b) -> Person.compareByAge(a, b)ラムダ式と同じです。それぞれ次のような特徴があります。

  • その仮パラメータリストはComparator<Person>.compare、からコピーされます(Person, Person)
  • 本体がPerson.compareByAgeメソッドを呼び出す

メソッド参照の種類:

メソッド参照には 4 つのタイプがあります。

ここに画像の説明を挿入
次のサンプルMethodReferencesExamplesには、最初の 3 種類のメソッド参照の例が含まれています。

import java.util.function.BiFunction;

public class MethodReferencesExamples {
    
    
    
    public static <T> T mergeThings(T a, T b, BiFunction<T, T, T> merger) {
    
    
        return merger.apply(a, b);
    }
    
    public static String appendStrings(String a, String b) {
    
    
        return a + b;
    }
    
    public String appendStrings2(String a, String b) {
    
    
        return a + b;
    }

    public static void main(String[] args) {
    
    
        
        MethodReferencesExamples myApp = new MethodReferencesExamples();

        // Calling the method mergeThings with a lambda expression
        System.out.println(MethodReferencesExamples.
            mergeThings("Hello ", "World!", (a, b) -> a + b));
        
        // Reference to a static method
        System.out.println(MethodReferencesExamples.
            mergeThings("Hello ", "World!", MethodReferencesExamples::appendStrings));

        // Reference to an instance method of a particular object        
        System.out.println(MethodReferencesExamples.
            mergeThings("Hello ", "World!", myApp::appendStrings2));
        
        // Reference to an instance method of an arbitrary object of a
        // particular type
        System.out.println(MethodReferencesExamples.
            mergeThings("Hello ", "World!", String::concat));
    }
}

すべてのステートメントSystem.out.println()の出力は同じです。Hello World!

BiFunctionjava.util.functionパッケージ内の多くの関数インターフェースの 1 つです。関数インターフェイスは、2 つの引数を受け取り、結果を生成するラムダ式またはメソッド参照を表すことができます。

静的メソッドの参照

メソッド参照Person::compareByAgeおよび はMethodReferencesExamples::appendstring静的メソッドへの参照です。

特定のオブジェクトのインスタンス メソッドへの参照

特定のオブジェクトのインスタンス メソッドを参照する例を次に示します。

class ComparisonProvider {
    
    
    public int compareByName(Person a, Person b) {
    
    
        return a.getName().compareTo(b.getName());
    }
        
    public int compareByAge(Person a, Person b) {
    
    
        return a.getBirthday().compareTo(b.getBirthday());
    }
}
ComparisonProvider myComparisonProvider = new ComparisonProvider();
Arrays.sort(rosterAsArray, myComparisonProvider::compareByName);

メソッド参照は、オブジェクトの一部であるmyComparisonProvider::compareByNameメソッドを呼び出します。JRE はメソッド タイプ パラメータを推論します。この場合は ですcompareByNamemyComparisonProvider(Person, Person)

同様に、メソッド参照は、オブジェクトの一部であるmyApp::appendStrings2メソッドを呼び出します。JRE はメソッド タイプ パラメータを推論します。この場合は ですappendStrings2myApp(String, String)

特定の型のオブジェクトのインスタンス メソッドを参照します。

以下は、特定の型の任意のオブジェクトのインスタンス メソッドへの参照の例です。

String[] stringArray = {
    
     "Barbara", "James", "Mary", "John",
    "Patricia", "Robert", "Michael", "Linda" };
Arrays.sort(stringArray, String::compareToIgnoreCase);

メソッドString::compareToIgnoreCase参照の同等のラムダ式には、仮パラメータのリストが含まれます(String a, String b)。ここで、 a と b は、この例をよりわかりやすく説明するために使用される任意の名前です。メソッド参照はa.compareToIgnoreCase(b)メソッドを呼び出します。

同様に、メソッド参照はメソッドString::concatを呼び出します。a.concat(b)

コンストラクターリファレンス

new という名前を使用すると、静的メソッドと同様にコンストラクターを参照できます。次のメソッドは、あるコレクションから別のコレクションに要素をコピーします。

public static <T, SOURCE extends Collection<T>, DEST extends Collection<T>>
    DEST transferElements(
        SOURCE sourceCollection,
        Supplier<DEST> collectionFactory) {
    
    
        
    DEST result = collectionFactory.get();
    for (T t : sourceCollection) {
    
    
        result.add(t);
    }
    return result;
}

関数型インターフェイスには、パラメーターを受け取らず、オブジェクトを返すSupplierメソッドが含まれていますgetしたがって、transferElements次のようにラムダ式を使用してメソッドを呼び出すことができます。

Set<Person> rosterSetLambda =
    transferElements(roster, () -> {
    
     return new HashSet<>(); });

次のように、ラムダ式の代わりにコンストラクター参照を使用できます。

Set<Person> rosterSet = transferElements(roster, HashSet::new);

PersonJava コンパイラは、タイプ の要素を含むコレクションを作成しようとしていると推定しましたHashSetあるいは、次のように指定することもできます。

Set<Person> rosterSet = transferElements(roster, HashSet<Person>::new);

おすすめ

転載: blog.csdn.net/chinusyan/article/details/130934246