匿名クラス ( 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
S
e1
e2
e1.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 は を返し、インスタンス上でメソッドを呼び出します。CheckPerson
tester
true
Person
printPersons
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
満たしていることを確認します。インスタンスがテスターによって指定された基準を満たしている場合、インスタンス上でメソッドが呼び出されます。Predicate
tester
Person
Person
printPerson
printPerson
メソッドを呼び出す代わりに、Person
テスターが指定した基準を満たすインスタンスに対して実行する別のアクションを指定できます。このアクションは、Lambda 式を使用して指定できます。Person
たとえば、1 つの引数 (型のオブジェクト) を受け取り、それを返すprintperson のようなラムダ式が必要だとしますvoid
。ラムダ式を使用するには、関数インターフェイスを実装する必要があることに注意してください。Person
この場合、型の 1 つのパラメーターを受け取り、それを返す抽象メソッドを含む関数型インターフェイスが必要ですvoid
。Consumer<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)
);
このメソッド呼び出しは次のことを行います。
- コレクションからオブジェクト ソースを取得します
source
。この場合、Person
オブジェクトのソースをコレクション名簿から取得します。コレクション ロスターはList
型のコレクション、およびIterable
型のオブジェクトであることに注意してください。 Predicate
オブジェクトtester
に一致するオブジェクトをフィルタリングします。この場合、Predicate
オブジェクトは、どのメンバーが選挙サービスに参加する資格があるかを指定するラムダ式です。- 各フィルター オブジェクトを、
Function
オブジェクトmapper
によって指定された値にマップします。この場合、Function
オブジェクトはlambda
メンバーの電子メール アドレスを返す式です。 - マップされた各オブジェクトに対して、
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 filter
、map
およびforEach
isAggregateOperation( 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 つの操作を定義します。この例では次のように出力されます。addition
subtraction
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!
BiFunction
java.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 はメソッド タイプ パラメータを推論します。この場合は です。compareByName
myComparisonProvider
(Person, Person)
同様に、メソッド参照は、オブジェクトの一部であるmyApp::appendStrings2
メソッドを呼び出します。JRE はメソッド タイプ パラメータを推論します。この場合は です。appendStrings2
myApp
(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);
Person
Java コンパイラは、タイプ の要素を含むコレクションを作成しようとしていると推定しましたHashSet
。あるいは、次のように指定することもできます。
Set<Person> rosterSet = transferElements(roster, HashSet<Person>::new);