Java は当初、完全なオブジェクト指向言語です。プリミティブ型を除いて、Java ではすべてがオブジェクトです。配列さえもオブジェクトです。各クラスのインスタンスはオブジェクトです。関数を個別に定義する可能性は 1 つもありません (クラスの外 -およそ transl. )。また、メソッドを引数として渡したり、別のメソッドの結果としてメソッド本体を返したりする方法はありません。そのようなものです。しかし、これは Java 8 より前の話です。 古き良き Swing の時代から、何らかの機能をメソッドに渡す必要がある場合は、匿名クラスを作成する必要がありました。たとえば、イベント ハンドラーを追加すると次のようになります。
someObject.addMouseListener(new MouseAdapter() {
public void mouseClicked(MouseEvent e) {
//Event listener implementation goes here...
}
});
ここでは、マウス イベント リスナーにコードを追加します。匿名クラスを定義しMouseAdapter
、そこからすぐにオブジェクトを作成しました。このようにして、追加の機能を に渡しましたaddMouseListener
。つまり、Java で単純なメソッド (機能) を引数で渡すのは簡単ではありません。この制限により、Java 8 開発者はラムダ式などの機能を言語仕様に追加する必要がありました。
なぜ Java にはラムダ式が必要なのでしょうか?
Java 言語は、最初から、注釈やジェネリックなどを除いて、あまり進化していません。まず第一に、Java は常にオブジェクト指向のままです。JavaScript などの関数型言語を使用すると、Java がどのように厳密にオブジェクト指向であり、厳密に型指定されているかが理解できます。Javaでは関数は必要ありません。それら自体を Java の世界で見つけることはできません。関数型プログラミング言語では、関数が前面に出てきます。それらは単独で存在します。それらを変数に代入し、引数を通じて他の関数に渡すことができます。JavaScript は関数型プログラミング言語の最良の例の 1 つです。関数型言語としての JavaScript の利点を詳しく説明した優れた記事がインターネット上にあります。関数型言語には Closure などの強力なツールがあり、従来のアプリケーション作成方法に比べて多くの利点があります。クロージャは、環境がアタッチされた関数です。つまり、関数のすべての非ローカル変数への参照を保存するテーブルです。Java では、ラムダ式を通じてクロージャをシミュレートできます。もちろん、クロージャとラムダ式には小さな違いがありますが、ラムダ式はクロージャの優れた代替品です。Steve Yegge は、皮肉で面白いブログの中で、Java の世界がどのように名詞 (エンティティ、オブジェクト -おおよその翻訳) に厳密に結びついているかを説明しています。彼のブログを読んだことがない方は、ぜひ読んでみてください。彼は、Lambda 式が Java に追加された正確な理由を面白く興味深い方法で説明しています。ラムダ式は、長い間欠けていた機能を Java にもたらします。ラムダ式は、オブジェクトと同じように言語に機能をもたらします。これは 100% 真実ではありませんが、ラムダ式はクロージャではありませんが、同様の機能を提供していることがわかります。関数型言語では、ラムダ式は関数です。しかし、Java では、ラムダ式はオブジェクトによって表され、関数インターフェイスと呼ばれる特定のオブジェクト タイプに関連付けられている必要があります。次にそれが何であるかを見ていきます。Mario Fusco の記事「Java にラムダ式が必要な理由」では、すべての最新言語にクロージャ機能が必要な理由が詳しく説明されています。ラムダ式の概要
ラムダ式は匿名関数です (Java にとって 100% 正しい定義ではないかもしれませんが、ある程度明確になります)。簡単に言えば、これは宣言のないメソッドです。アクセス修飾子なしで、値と名前を返します。つまり、メソッドを作成してすぐに使用できるようになります。これは、1 回限りのメソッド呼び出しの場合に特に便利です。クラスを作成する必要がなく、メソッドの宣言と作成にかかる時間が短縮されます。Java のラムダ式は通常、次の構文を持ちます(аргументы) -> (тело)
。例えば:
(арг1, арг2...) -> { тело }
(тип1 арг1, тип2 арг2...) -> { тело }
以下に実際のラムダ式の例をいくつか示します。
(int a, int b) -> { return a + b; }
() -> System.out.println("Hello World");
(String s) -> { System.out.println(s); }
() -> 42
() -> { return 3.1415 };
ラムダ式の構造
ラムダ式の構造を勉強してみましょう。- ラムダ式には 0 個以上の入力パラメーターを含めることができます。
- パラメータのタイプは明示的に指定することも、コンテキストから取得することもできます。たとえば (
int a
) は次のように書くことができます (a
) - パラメータは括弧で囲まれ、カンマで区切られます。たとえば、(
a, b
) または (int a, int b
) または (String a
、int b
、float c
) - パラメータがない場合は、空の括弧を使用する必要があります。例えば
() -> 42
- パラメータが 1 つだけの場合、型が明示的に指定されていない場合は、括弧を省略できます。例:
a -> return a*a
- Lambda 式の本文には 0 個以上の式を含めることができます。
- 本体が 1 つのステートメントで構成されている場合、中括弧で囲むことはできません。また、戻り値はキーワードなしで指定できます
return
。 - それ以外の場合は、中括弧が必要であり (コードのブロック)、戻り値はキーワードを使用して最後に指定する必要があります
return
(それ以外の場合、戻り値の型は になりますvoid
)。
関数型インターフェースとは
Java では、マーカー インターフェイスはメソッドやフィールドを宣言しないインターフェイスです。言い換えれば、トークン インターフェイスは空のインターフェイスです。同様に、関数型インターフェイスは、抽象メソッドが 1 つだけ宣言されたインターフェイスです。java.lang.Runnable
は、機能インターフェイスの例です。宣言するメソッドは 1 つだけですvoid run()
。インターフェースもありActionListener
、機能的です。以前は、関数インターフェイスを実装するオブジェクトを作成するには、匿名クラスを使用する必要がありました。ラムダ式を使用すると、すべてが簡単になります。各ラムダ式は、何らかの関数インターフェイスに暗黙的にバインドできます。たとえば、Runnable
次の例に示すように、インターフェイスへの参照を作成できます。
Runnable r = () -> System.out.println("hello world");
この種の変換は、関数インターフェイスを指定しない場合、常に暗黙的に行われます。
new Thread(
() -> System.out.println("hello world")
).start();
Runnable
上記の例では、コンパイラはクラス コンストラクターThread
:からのインターフェイスの 実装としてラムダ式を自動的に作成しますpublic Thread(Runnable r) { }
。ラムダ式と対応する関数インターフェイスの例をいくつか示します。
Consumer<Integer> c = (int x) -> { System.out.println(x) };
BiConsumer<Integer, String> b = (Integer x, String y) -> System.out.println(x + " : " + y);
Predicate<String> p = (String s) -> { s == null };
@FunctionalInterface
Java 言語仕様に従って Java 8 に追加された アノテーションは、宣言されたインターフェイスが機能するかどうかをチェックします。さらに、Java 8 には、Lambda 式で使用する既製の関数インターフェイスが多数含まれています。@FunctionalInterface
宣言されたインターフェイスが機能しない場合は、コンパイル エラーがスローされます。以下は、関数インターフェイスを定義する例です。
@FunctionalInterface
public interface WorkerInterface {
public void doSomeWork();
}
定義が示すように、関数型インターフェイスには抽象メソッドを 1 つだけ含めることができます。別の抽象メソッドを追加しようとすると、コンパイル エラーが発生します。例:
@FunctionalInterface
public interface WorkerInterface {
public void doSomeWork();
public void doSomeMoreWork();
}
エラー
Unexpected @FunctionalInterface annotation @FunctionalInterface ^ WorkerInterface is not a functional interface multiple non-overriding abstract methods found in interface WorkerInterface 1 error После определения функционального интерфейса, мы можем его использовать и получать все преимущества Lambda-выражений. Пример:
// defining a functional interface @FunctionalInterface public interface WorkerInterface { public void doSomeWork(); }
public class WorkerInterfaceTest {
public static void execute(WorkerInterface worker) {
worker.doSomeWork();
}
public static void main(String [] args) {
// calling the doSomeWork method via an anonymous class
// (classic)
execute(new WorkerInterface() {
@Override
public void doSomeWork() {
System.out.println("Worker called via an anonymous class");
}
});
// calling the doSomeWork method via Lambda expressions
// (Java 8 new)
execute( () -> System.out.println("Worker called via Lambda") );
}
}
結論:
Worker вызван через анонимный класс
Worker вызван через Lambda
ここでは独自の関数インターフェイスを定義し、ラムダ式を使用しました。このメソッドexecute()
はラムダ式を引数として受け入れることができます。
ラムダ式の例
Lambda 式を理解する最良の方法は、いくつかの例を参照することです。 ストリームはThread
2 つの方法で初期化できます。
// Old way:
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("Hello from thread");
}
}).start();
// New way:
new Thread(
() -> System.out.println("Hello from thread")
).start();
Java 8 のイベント管理は、Lambda 式を通じて行うこともできます。イベント ハンドラーをActionListener
UI コンポーネントに追加するには、次の 2 つの方法があります。
// Old way:
button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("Button pressed. Old way!");
}
});
// New way:
button.addActionListener( (e) -> {
System.out.println("Button pressed. Lambda!");
});
指定された配列のすべての要素を表示する簡単な例。ラムダ式を使用する方法は複数あることに注意してください。以下では、矢印構文を使用して通常の方法でラムダ式を作成し、(::)
Java 8 では通常のメソッドをラムダ式に変換する二重コロン演算子も使用します。
// Old way:
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7);
for(Integer n: list) {
System.out.println(n);
}
// New way:
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7);
list.forEach(n -> System.out.println(n));
// New way using double colon operator ::
list.forEach(System.out::println);
次の例では、関数インターフェイスを使用してPredicate
テストを作成し、そのテストに合格した項目を出力します。このようにして、ラムダ式にロジックを組み込み、それに基づいて処理を行うことができます。
import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;
public class Main {
public static void main(String [] a) {
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7);
System.out.print("Outputs all numbers: ");
evaluate(list, (n)->true);
System.out.print("Does not output any number: ");
evaluate(list, (n)->false);
System.out.print("Output even numbers: ");
evaluate(list, (n)-> n%2 == 0 );
System.out.print("Output odd numbers: ");
evaluate(list, (n)-> n%2 == 1 );
System.out.print("Output numbers greater than 5: ");
evaluate(list, (n)-> n > 5 );
}
public static void evaluate(List<Integer> list, Predicate<Integer> predicate) {
for(Integer n: list) {
if(predicate.test(n)) {
System.out.print(n + " ");
}
}
System.out.println();
}
}
結論:
Выводит все числа: 1 2 3 4 5 6 7
Не выводит ни одного числа:
Вывод четных чисел: 2 4 6
Вывод нечетных чисел: 1 3 5 7
Вывод чисел больше 5: 6 7
ラムダ式をいじることで、リストの各要素の二乗を表示できます。stream()
通常のリストをストリームに変換するメソッドを使用していることに注意してください。Java 8 は素晴らしいクラスStream
( java.util.stream.Stream
) を提供します。ラムダ式を使用できる便利なメソッドが多数含まれています。x -> x*x
ラムダ式をメソッドに渡しmap()
、それをストリーム内のすべての要素に適用します。その後、 を使用してforEach
リストのすべての要素を出力します。
// Old way:
List<Integer> list = Arrays.asList(1,2,3,4,5,6,7);
for(Integer n : list) {
int x = n * n;
System.out.println(x);
}
// New way:
List<Integer> list = Arrays.asList(1,2,3,4,5,6,7);
list.stream().map((x) -> x*x).forEach(System.out::println);
リストが与えられた場合、リストのすべての要素の二乗和を出力する必要があります。ラムダ式を使用すると、たった 1 行のコードを記述するだけでこれを実現できます。この例では、コンボリューション (リダクション) 法を使用しますreduce()
。各要素を 2 乗するメソッドを使用しmap()
てから、reduce()
すべての要素を 1 つの数値に折りたたむメソッドを使用します。
// Old way:
List<Integer> list = Arrays.asList(1,2,3,4,5,6,7);
int sum = 0;
for(Integer n : list) {
int x = n * n;
sum = sum + x;
}
System.out.println(sum);
// New way:
List<Integer> list = Arrays.asList(1,2,3,4,5,6,7);
int sum = list.stream().map(x -> x*x).reduce((x,y) -> x + y).get();
System.out.println(sum);
ラムダ式と匿名クラスの違い
主な違いは、キーワードの使用ですthis
。匿名クラスの場合、「 」キーワードはthis
匿名クラスのオブジェクトを示しますが、ラムダ式では、「this
」はラムダ式が使用されるクラスのオブジェクトを示します。もう 1 つの違いは、コンパイル方法です。Java はラムダ式をコンパイルし、private
クラス メソッドに変換します。これは、動的メソッド バインディングのために Java 7 で導入されたinvokedynamic命令を使用します。Tal Weiss は、Java がラムダ式をバイトコードにコンパイルする方法をブログで説明しました
GO TO FULL VERSION