English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية
最初にLambda表現に出会ったのはTypeScript(JavaScriptのスーパーセット)の中で、当時はTypeScriptのthisメソッドがこのメソッド内ではなく外で使用されるようにするためにでした。使用した後、突然LambdaがJDK8の重い新機能かどうか感じて、関連する資料を調べ記録しました:
一. 行動パラメータ化
行動パラメータ化とは、簡単に言えば、関数の主体がテンプレートクラスの一般的なコードのみを含んでおり、ビジネスシーンに応じて変化するロジックは関数にパラメータとして渡されることです。行動パラメータ化を用いることで、プログラムはより一般的になり、頻繁な変更に対応することができます。
あるビジネスシーンを考える、例えば、私たちがプログラムでリンゴをフィルタリングする必要がある場合、まずリンゴのエンティティを定義します:
public class Apple { /** ID */ private long id; /** 色 */ private Color color; /** 重量 */ private float weight; /** 産地 */ private String origin; public Apple() { } public Apple(long id, Color color, float weight, String origin) { this.id = id; this.color = color; this.weight = weight; this.origin = origin; } // getterおよびsetterの省略 }
ユーザーの最も基本的な要求は、プログラムを通じて緑色のリンゴをフィルタリングできることです。したがって、プログラムを通じて迅速に実現できます:
public static List<Apple> filterGreenApples(List<Apple> apples) { List<Apple> filterApples = new ArrayList<>(); for (final Apple apple : apples) { if (Color.GREEN.equals(apple.getColor())) { filterApples.add(apple); } } return filterApples; }
このコードはとてもシンプルで、特に言及する価値のあることはありません。しかし、ユーザーの要求が緑色に変わった場合、コードの修正も簡単で、判断条件の緑色を赤色に変更するだけで済みます。しかし、変化条件が頻繁に変わる場合はどうでしょうか?63;色の変更だけなら、ユーザーが色の判断条件を直接渡して、判断メソッドの引数に「判断する集合およびフィルタリングする色」を指定します。しかし、ユーザーが色だけでなく、重量や大きさなどを判断したい場合、どうすればいいですか?あなたはそれぞれ異なるパラメータを追加して判断を完了することができますか?本当にパラメータの渡し方で良いのでしょうか?フィルタ条件が増えるにつれて、組み合わせパターンがより複雑になるため、すべての状況を考慮し、それぞれの状況に対して対応策を持つ必要がありますか?63;この時点で、行動をパラメータ化し、フィルタ条件をパラメータとして渡すことができます。その場合、判断インターフェースを包装することができます:
public interface AppleFilter { /** * フィルタリング条件の抽象化 * * @param apple * @return */ boolean accept(Apple apple); } /** * フィルタ条件をインターフェースに包装します * * @param apples * @param filter * @return */ public static List<Apple> filterApplesByAppleFilter(List<Apple> apples, AppleFilter filter) { List<Apple> filterApples = new ArrayList<>(); for (final Apple apple : apples) { if (filter.accept(apple)) { filterApples.add(apple); } } return filterApples; }
上記の行動を抽象化することで、具体的な呼び出しの場でフィルタリング条件を設定し、条件をメソッドにパラメータとして渡すことができます。この場合、匿名内部クラスのメソッドを使用します:
public static void main(String[] args) { List<Apple> apples = new ArrayList<>(); // リンゴをフィルタリング List<Apple> filterApples = filterApplesByAppleFilter(apples, new AppleFilter() { @Override public boolean accept(Apple apple) { // 重量が大きいリンゴをフィルタリング100gの赤いリンゴ return Color.RED.equals(apple.getColor()) && apple.getWeight() > 100; } }); }
この設計はJDK内部でもよく用いられています。例えば、Java.util.Comparator、java.util.concurrent.Callableなど、このようなインターフェースを使用する際には、具体的な実行ロジックを指定するために匿名クラスを使用することができますが、上記のコードブロックから見ると、非常にエキゾチックですが、Javaでは簡潔ではありません。8以下では、lambdaを使用して簡略化することができます:
// リンゴをフィルタリング List<Apple> filterApples = filterApplesByAppleFilter(apples, (Apple apple) -> Color.RED.equals(apple.getColor()) && apple.getWeight() >= 100); //()->xxx ()内がメソッドのパラメータです、xxxがメソッドの実装です
二. lambda式の定義
lambda式は、簡潔で伝達可能な匿名関数として定義できます。まず、lambda式は本質的に関数であることを明確にする必要があります。特定のクラスに属していないにもかかわらず、パラメータリスト、関数本体、返り値の型、例外を投げることができる機能を持ちます。次に、匿名であるため、lambda式には具体的な関数名がありません。lambda式はパラメータのように伝達可能であり、コードの記述を大幅に簡素化できます。フォーマットは以下の通りです:
フォーマット一:パラメータリスト -> 表現
フォーマット二:パラメータリスト -> {式集合}
注意すべき点是、lambda式は暗黙のreturnキーワードを含んでいるため、単一の式ではreturnキーワードを明示的に書かなくても良いですが、式が式集合である場合、明示的にreturnを追加し、複数の式を括弧{ }で囲む必要があります。以下にいくつかの例を挙げます:
//指定された文字列の長さを返します。暗黙のreturn文が含まれています。 (String s) -> s.length() // 常に返します42の無引数メソッド () -> 42 // 複数行の式が含まれる場合、花括号で囲みます (int x, int y) -> { int z = x * y; return x + z; }
三. 機能型インターフェースを使用してlambda式を依存させる
lambda式の使用は機能型インターフェースを介して行う必要があり、したがって、機能型インターフェースが存在する場所だけにlambda式を簡略化することができます。
カスタム機能型インターフェース:
機能型インターフェースは、抽象メソッドを持つインターフェースと定義されています。java8インターフェース定義上の改善は、デフォルトメソッドの導入で、インターフェース内でメソッドのデフォルト実装を提供できるようにすることです。しかし、デフォルトメソッドがいくつかある場合でも、抽象メソッドが一つしかない限り、それは機能型インターフェースです。以下(上記のAppleFilterを参照)がその例です:
/** * リンゴフィルタリングインターフェース */ @FunctionalInterface public interface AppleFilter { /** * フィルタリング条件の抽象化 * * @param apple * @return */ boolean accept(Apple apple); }
AppleFilterは一つの抽象メソッドaccept(Apple apple)を含んでおり、定義に従って機能型インターフェースとして見ることができます。定義時に@FunctionalInterfaceアノテーションを追加し、このインターフェースが機能型インターフェースであることを示します。ただし、このアノテーションはオプションです。このインターフェースが追加された場合、コンパイラはこのインターフェースが抽象メソッドを一つだけ持つことを制限し、それ以外の場合はエラーを報告します。したがって、機能型インターフェースにアノテーションを追加することをお勧めします。
jdkの機能型インターフェース:
jdkはlambda式に豊富な機能型インターフェースを内蔵しており、以下では、Predicate<T>、Consumer<T>、Function<T, R>の使用例を説明します。
Predicate:
@FunctionalInterface public interface Predicate<T> { /** * このpredicateに対して指定された引数を評価します。 * * @param t 入力引数 * @return 引数がpredicateに一致する場合{@code true} * それ以外の場合は{@code false} */ boolean test(T t); }
Predicateの機能は上記のAppleFilterに似ており、外部で設定した条件を用いて传入する引数を検証し、検証結果のbooleanを返します。以下では、Predicateを使用してList集合の要素をフィルタリングする方法を示します:
/** * * @param list * @param predicate * @param <T> * @return */ public <T> List<T> filter(List<T> list, Predicate<T> predicate) { List<T> newList = new ArrayList<T>(); for (final T t : list) { if (predicate.test(t)) { newList.add(t); } } return newList; }
使用:
demo.filter(list, (String str -> null != str && !str.isEmpty());
Consumer
@FunctionalInterface public interface Consumer<T> { /** * この操作を実行します。 * * @param t 入力引数 */ void accept(T t); }
Consumerはaccept抽象関数を提供しており、引数を受け取りますが値を返しません。以下ではConsumerを使用してコレクションを巡回します。
/** * コレクションを巡回し、カスタム行動を実行します。 * * @param list * @param consumer * @param <T> */ public <T> void filter(List<T> list, Consumer<T> consumer) { for (final T t : list) { consumer.accept(t); } }
上記の機能インターフェースを使用して、文字列コレクションを巡回し、空の文字列を印刷します:
demo.filter(list, (String str -> { if (StringUtils.isNotBlank(str)) { System.out.println(str); } });
Function
@FunctionalInterface public interface Function<T, R> { /** * この関数を指定された引数に適用します。 * * @param t 函数引数 * @return 函数結果 */ R apply(T t); }
Funcationを実行し、変換操作を行います。入力はタイプTのデータで、Rタイプのデータを返します。以下ではFunctionを使用してコレクションを変換します:
public <T, R> List<R> filter(List<T> list, Function<T, R> function) { List<R> newList = new ArrayList<R>(); for (final T t : list) { newList.add(function.apply(t)); } return newList; }
その他:
demo.filter(list, (String str -> Integer.parseInt(str));
上記の機能インターフェースは、一部の論理操作のデフォルト実装も提供しています。詳細は後で説明します。8インターフェースのデフォルトメソッドについては、後で説明します。
使用中に注意すべき点:
型推論:
コーディングプロセス中に、私たちの呼び出しコードがどの機能インターフェースと一致するか疑問に思うことがありますが、実際にはコンパイラはパラメータ、返り値の型、例外の型(存在する場合)などに基づいて正しく判定を行います。
具体的な呼び出し時には、パラメータの型を省略することで、さらにコードを簡素化できます:
// リンゴをフィルタリング List<Apple> filterApples = filterApplesByAppleFilter(apples, (Apple apple) -> Color.RED.equals(apple.getColor()) && apple.getWeight() >= 100); // 一部の情况下、パラメータの型を省略することもできます。コンパイラはコンテキストから正しく判断します。 List<Apple> filterApples = filterApplesByAppleFilter(apples, apple -> Color.R > ED.equals(apple.getColor()) && apple.getWeight() >= 100);
ローカル変数
上記のすべての例では、ラムダ式は主体のパラメータを使用していますが、ラムダ式内でローカル変数を使用することもできます。以下に例を示します:
int weight = 100; List<Apple> filterApples = filterApplesByAppleFilter(apples, apple -> Color.RED.equals(apple.getColor()) && apple.getWeight() >= weight);:
この例では、ラムダ式でローカル変数weightを使用していますが、ラムダ式内でローカル変数を使用する場合、その変数が明示的にfinalとして宣言されているか、実際にはfinalである必要があります。これは、ローカル変数がスタック上に格納されているため、ラムダ式は別のスレッドで実行されるため、そのスレッドがこのローカル変数にアクセスしようとするときに、変数が変更されたり、回収されたりする可能性があるためです。そのため、final修飾子を使用することで、スレッドセーフな問題が発生しません。
4. メソッド参照
メソッド参照を使用することで、コードをさらに簡素化できます。時には、この簡素化がコードをより直感的に見せることができます。まず、一例を見てみましょう:
/* ... applesの初期化操作を省略しています */ // ラムダ式を使用しています apples.sort((Apple a, Apple b)}) -> Float.compare(a.getWeight(), b.getWeight())); // メソッドリファレンスを使用して apples.sort(Comparator.comparing(Apple::getWeight));
メソッドリファレンスは::を使用してメソッドの所属とメソッド自身を結びつけます。主に三つのカテゴリーに分けられます:
静的メソッド
(args) -> ClassName.staticMethod(args)
を変換します
ClassName::staticMethod
のインスタンスメソッド
(args) -> args.instanceMethod()
を変換します
> ClassName::instanceMethod // ClassNameはargsのタイプ
外部のインスタンスメソッド
(args) -> ext.instanceMethod(args)
を変換します
ext::instanceMethod(args)
参考:
http://www.codeceo.com/article/lambda-of-java-8.html
以上は編集者が皆さんに紹介したJDKです。8新しい機能のLambda式は皆さんに役立つことを願っています。何かご不明な点があれば、コメントを残してください。編集者はすぐに回答します。皆様の呐喊教程サイトへのサポートに感謝します。
声明:この記事の内容はインターネットから取得しており、著作権者に帰属します。インターネットユーザーにより自発的に提供された内容であり、本サイトは所有権を有しないです。また、人工的に編集された内容ではありませんし、関連する法的責任を負いません。著作権侵害を疑われる内容がある場合は、メールを送信して:notice#oldtoolbag.com(メールを送信する際には、#を@に変更してください。報告を行い、関連する証拠を提供してください。一旦確認が取れましたら、本サイトは即座に侵害を疑われる内容を削除します。)