「Javaのラムダ式ってなんだろう?」
「ラムダ式ってどうやって書くの?」
「具体的なコーディング例を教えて欲しい」
Javaのラムダ式の役割や記述方法に関して知っていますか?
Javaのラムダ式を使うと、コードの記述量を減らせ、可読性と保守性が向上します。
初心者にとっては初めて見ると複雑そうに見えるかもしれませんが、インプットできればさまざまな実装に役立ちます。
そこで、この記事ではJavaのラムダ式に焦点を当て、サンプルコードをもとに基本から応用までを解説します。
ラムダ式を使う際の注意点もまとめているので、ぜひ最後までをご覧ください。
Javaのラムダ式とは?
Javaのラムダ式(Lambda Expression)は、Java 8で導入された機能で、メソッドを単なる変数のように扱う新しい記述様式です。
この章では、Javaのラムダ式に関して理解を深めるために以下の項目を説明していきますね。
- Javaのラムダ式を活用する利点
- クラスからラムダ式への進化
- Java8以降の新機能とラムダ式の関連
後ほど、ラムダ式の書き方は解説するので、この時点でラムダ式を理解しなくても大丈夫です。
大まかなラムダ式の役割を理解していきましょう。
Javaのラムダ式を活用する利点
Javaのラムダ式の利点は、コードを簡潔にし、読みやすくすることです。
ラムダ式を使用することで、開発者はより少ないコードで同じ機能を実装でき、プログラムの可読性と保守性が向上します。
ラムダ式を使用すると、関数の名前を付ける必要がなく、一度しか使用しない機能を簡単に記述可能です。
また、クラスの定義やインスタンス生成など、複雑なプロセスを省略できるため、コードの量が減ります。
たとえば、数値のリストから特定の条件を満たす要素を選択して表示する簡単なプログラムを考えてみましょう。
ラムダ式を使うと、この処理は次のように簡単に記述できます。
//ラムダ式を使わない場合
import java.util.Arrays;
import java.util.List;
public class Main {
public static void main(String[] args) {
List numbers = Arrays.asList(1, 2, 3, 4, 5);
numbers.stream().filter(new EvenNumberFilter()).forEach(new EvenNumberPrinter());
}
}
class EvenNumberFilter implements Predicate {
@Override
public boolean test(Integer number) {
return number % 2 == 0;
}
}
class EvenNumberPrinter implements Consumer {
@Override
public void accept(Integer number) {
System.out.println(number);
}
}//出力結果:2,4
//—----------------------------------------------------------------------------------------------------
//ラムダ式を用いた場合
List numbers = Arrays.asList(1, 2, 3, 4, 5);
numbers.stream()
.filter(number -> number % 2 == 0)
.forEach(System.out::println);//出力結果:2,4
サンプルコードでは、2で割り切れる数値(偶数)だけを選んで表示しています。
見てわかる通り、同じ処理を実行するにはラムダ式を使う場合より多くのコードが必要になり、複雑さが増しますね。
ラムダ式のもう1つの利点は、関数を引数として渡したり、戻り値として使用したりできる点です。
プログラムの柔軟性が高まり、より効果的なコードを書けます。
ラムダ式はJavaプログラミングを学ぶ初心者にとっても、コードをシンプルにし、理解を深めるのに役立ちますよ。
ラムダ式で記述することでアプリケーションのパフォーマンスも向上する場合もあります。
クラスからラムダ式への進化
従来、Javaでは特定の機能を実行するために、以下のようにインターフェースの実装という形でクラスを定義する必要がありました。
// 関数型インターフェース
public interface SomeInterface {
String getMessage(int id, String name);
}
public void someMethod() {
// 匿名クラスで実装
SomeInterface someIF = new SomeInterface() {
public String getMessage(int id, String name) {
return "Your id = " + id + ", name = " + name;
}
};
System.out.println(someIF.getMessage(10, "Alice"));
}
```
```java
//出力結果
Your id = 10, name = Alice
従来の方法だとコード量が多くなってしまい、デバッグでエラー箇所や機能追加する場合、複雑に感じるでしょう。
これがラムダ式の導入により、インターフェースの具体的なメソッド実装を簡潔な式で記述できるようになりました。
以下が同じ出力結果になるようにラムダ式で書き換えたコードです。
public void someMethod() {
// ラムダ式で置き換え
SomeInterface someIF = (id, name) -> "Your id = " + id + ", name = " + name;
System.out.println(someIF.getMessage(10, "Alice"));
}
ラムダ式では、引数の型が自動的に推論され、型を省略可能です。
ラムダ式は、抽象メソッドを1つだけ持つインターフェース(関数型インターフェース)に対してのみ使用でき、長い匿名クラスの実装を1行で表現できます。
Java8以降の新機能とラムダ式の関連
Java 8の登場は、Javaプログラミングにおける革命的な変化を与えました。
中でもラムダ式は、特にコレクションの操作においてその強みを発揮します。
Java 8以前では、コレクション内の要素を操作するためには、通常、forループやwhileループを使用していました。
import java.util.Arrays;
import java.util.List;
public class TraditionalExample {
public static void main(String[] args) {
List names = Arrays.asList("Alice", "Bob", "Charlie");
// forループを使用してリストの各要素を出力
for (String name : names) {
System.out.println(name);
}
}
}
//出力結果
Alice
Bob
Charlie
上記のコードは、直感的でわかりやすいですが、同じタイプの操作を繰り返す場合、コード量が増える傾向があります。
同じ出力結果になるようにラムダ式で記述すると、以下のようになります。
import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;
public class LambdaExample {
public static void main(String[] args) {
List names = Arrays.asList("Alice", "Bob", "Charlie");
// ラムダ式でリストの各要素を出力
names.forEach(name -> System.out.println(name));
}
}
forループを使う代わりに、forEachメソッドとラムダ式を用いてリスト内の各要素を処理しています。
このように、ラムダ式はJava 8以降の新機能と組み合わせて可読性を向上させるのに役立ちますよ。
ラムダ式の基本文法
ラムダ式の書式は、与える引数に処理を実行し結果を返すもので、基本的な文法は以下の通りです。
(引数1, 引数2, ...) -> { 処理内容; return 戻り値; }
ラムダ式では引数の型を省略できます。コンパイラが自動的に型を推論するため、明示的な型の記述は必要ありません。
また、ラムダ式は引数の数によって、記述方法を変えられます。以下を覚えておきましょう。
-引数が1つの場合、丸括弧`( )`を省略できる( `引数 -> 処理内容;`)
-引数がない場合、丸括弧`( )`は省略できない(`() -> 処理内容;`)
-引数が2つ以上ある場合、丸括弧`( )`は省略できない(`() -> 処理内容;`)
それぞれサンプルコードをもとに理解を深めていきましょう。
以下が引数1つの場合に丸括弧を省略したサンプルコードです。
Consumer shout = s -> System.out.println(s.toUpperCase());
shout.accept("hello"); // "HELLO"を出力
引数がない場合は、丸括弧が省略できないので以下のようになります。
Runnable greet = () -> System.out.println("Hello World!");
greet.run(); // "Hello World!"を出力
引数が2つ以上ある場合は、丸括弧が必ず必要なので注意しましょう。
//引数が2つの場合
BiConsumer add = (a, b) -> System.out.println(a + b);
add.accept(5, 7); // 12を出力
また、ラムダ式は処理内容が1行のみの場合、波括弧`{}`と`return`が省略可能です。
Predicate isPositive = number -> number > 0;
System.out.println(isPositive.test(5)); // trueを出力
上記のサンプルコードでは`number > 0 `という条件判定が処理内容となっています。
このように、引数の数や処理の複雑さに応じて柔軟に構文を使い分けることで、より効率的で読みやすいコードを書くことが可能です。
Javaラムダ式の関数型インターフェース
関数型インターフェースは、抽象メソッドを1つだけ持つインターフェースです。1つの抽象メソッドは、ラムダ式によって実装されます。
以下の関数型に関して、それぞれ解説していきますね。
- Function
- Consumer
- Predicate
一見難しく感じてしまうかもしれませんが、それぞれの関数型の特徴さえ覚えてしまえば簡単に記述できますよ。
Function
Functionは、Javaの関数型インターフェースの1つで、入力値を別の値に変換する処理を定義します。
- T: メソッドの引数の型
- R: メソッドの戻り値の型
さまざまな型のデータ変換や操作を一つのインターフェースでラムダ式を利用することで、複雑な匿名クラスの代わりに、簡潔で読みやすいコードを記述できます。
以下のサンプルコードを見てください。
Function asterisker = number -> "*" + number;
String result = asterisker.apply(10);
System.out.println(result); // 出力: *10
Functionは整数の型`Integer`を受け取り、それを文字列`String`に変換しています。
`apply`メソッドを使って関数を実行し、10という整数を渡すと、「10」という文字列が返される処理です。
他にも、BiFunctionを使うと、2つの引数を受け取り、それらを処理して結果を返す関数を定義できます。
以下が`BiFunctionのサンプルコードです。
BiFunction adderToString = (a, b) -> String.valueOf(a + b);
String result = adderToString.apply(1, 2);
System.out.println(result); // 出力: "3"
BiFunctionは、2つの整数の型`Integer`を受け取り、それらを足し合わせて文字列`String`を返しています。
String.valueOf(a + b) は、2つの整数 a と b を加算し、その結果を文字列に変換していますね。
adderToString.apply(1, 2) は、`adderToString `関数に 1 と 2 という整数を引数として渡し、それを実行します。
`BiFunction `とラムダ式を組み合わせることで、複雑な処理をシンプルに記述できることがわかりますね。
Consumer
Consumerは、引数を受け取り、それを使って何らかのアクションを実行するのに適した関数型インターフェースです。
- T:Consumer が処理するオブジェクトの型
`Consumer`によって行われる操作は、引数を変更したり、何かのアクションを実行したりしますが、戻り値はないので副作用を起こす目的で使用されます。
以下の例では、`listProcessor `というList型の Consumer が定義されており、リストに新しい要素を追加する処理を実行しています。
Consumer > listProcessor = list -> list.add("新しい要素");
List myList = new ArrayList<>(Arrays.asList("要素1", "要素2"));
listProcessor.accept(myList);
System.out.println(myList); // 出力: [要素1, 要素2, 新しい要素]
ラムダ式` list -> list.add(“新しい要素”) `は、与えられたリストに “新しい要素” を追加する命令を表しています。
最終的に Consumer に` myList ` を渡すと、` myList ` に “新しい要素” が追加される処理となっていますね。
また、複数の引数を扱う場合は`BiConsumer
サンプルコードを見てイメージをつけていきましょう。
以下の例の` BiConsumerは、` String ` 型と ` Integer ` 型の2つの異なる引数を受け取り、名前と年齢を出力する処理を行っています。
BiConsumer infoPrinter = (name, age) -> System.out.println(name + "は" + age + "歳です。");
infoPrinter.accept("太郎", 25); // 出力: 太郎は25歳です。
infoPrinterは、情報を出力するために使用される関数です。
このコードでは、 `infoPrinter.accept`で受け取った名前(name)と年齢(age)に基づいて
メッセージを出力しています。
Predicate
Predicateは、一つの引数を受け取り、その引数に対する真偽値(trueまたはfalse)を返す関数型インターフェースです。
- T: Predicate が真偽をチェックするオブジェクトの型
以下のサンプルコードでは、文字列が “Java” であるかどうかを判定する Predicate を定義しています。
Predicate isJava = text -> "Java".equals(text);
boolean result = isJava.test("Java");
System.out.println(result); // 出力: true
定義されているisJavaは、ラムダ式 ` text -> “Java”.equals(text) ` を使用して与えられた文字列 text が “Java” と等しいかどうかを判断する条件を表していますよ。
他の例もみてみましょう。以下は、Predicateを用いて整数が正の数かどうかを判定する Predicate を使っています。
Predicate isPositive = number -> number > 0;
System.out.println(isPositive.test(5)); // 出力: true
`isPositive` という Predicate は、ラムダ式 `number -> number > 0 `を使用して与えられた整数 `number `が正の数であるかどうかを判断する条件を表しています。
`isPositive.test(5) `は、`isPositive `に 5 という整数を渡して、この条件に一致するか判定しており、5 は正の数なので、trueです。
このように、ラムダ式を使って、条件判断のロジックを容易に記述できますよ。
Javaラムダ式の応用的な使い方
Javaのラムダ式の基本が理解できたら、より応用的なラムダ式使い方をマスターしていきましょう。
Javaのラムダ式をさらに応用的に使用するための以下の方法を紹介します。
- メソッド参照
- 関数型インターフェースのカスタマイズ
応用的なラムダ式の使い方を習得することで、より発展的な処理ができるようになります。
メソッド参照
メソッド参照は、単一のメソッド呼び出しをさらに短い形式で書くための方法です。
具体的には`ラムダ式 (引数) -> クラス.メソッド(引数) `を、もっと短く `クラス::メソッド `として書くことができます。
特定の文字列で始まるかをチェックする以下のサンプルコードを見てください。
String prefix = "test";
Predicate startsWithTest = prefix::startsWith;
System.out.println(startsWithTest.test("testing")); // 出力: true
`startsWithTest `は `prefix `文字列の `startsWith `メソッドを参照しているんです。
参照することで、別の文字列が `prefix `の文字列である”test”で始まっているか判定しています。
このように、より少ない記述で同じ機能を実現させられますよ。
関数型インターフェースのカスタマイズ
先ほども触れましたが、関数型インターフェースとは、1つの抽象メソッドを持つインターフェースです。
これをカスタマイズすることで、独自の処理や条件を持つメソッドを作成できます。
サンプル例を見て、イメージを深めていきましょう。
以下では、 `StringProcessor ` という名前で新しいインターフェースを定義しています。
@FunctionalInterface
public interface StringProcessor {
String process(String input);
}
process は文字列を受け取り、加工して別の文字列を返すメソッドですよ。
そして、以下では `StringProcessor ` インターフェースをラムダ式で実装しています。
StringProcessor reverser = str -> new StringBuilder(str).reverse().toString();
System.out.println(reverser.process("Java")); // 出力: avaJ
reverser は、受け取った文字列を逆順にする処理を行うメソッドですよ。
`StringProcessor `インターフェースの ` process `メソッドを `reverser `が実装しているため”Java” という文字列を逆順にして “avaJ” として返す処理になっています。
一度作成したインターフェースは、使用するメソッドの形式が明確になるので再利用可能なコードを作成できる点がメリットです。
Javaラムダ式に関する注意点
ラムダ式は、を使用する際には、以下の注意点があります。
- ラムダ式での変数の取り扱い
- ラムダ式と例外処理
説明を進めていきましょう。
ラムダ式での変数の取り扱い
ラムダ式は匿名クラスと同様に、スコープ外の変数を参照する場合、その変数はfinalまたは事実上のfinalである必要があります。
これは、ラムダ式が外部変数を「キャプチャ」するときのJavaのルールによるものだからです。
ラムダ式の内部または外部で、参照される変数を変更するとコンパイルエラーになってしまいうので注意しましょう。
以下のコンパイルエラーを起こすサンプルコードです。
public class Main {
public static void main(String[] args) {
int n = 8; // 事実上のfinal変数
InterfaceTest it = name -> "Hello " + name + n + "!";
System.out.println(it.method("Java"));
// n = 10; // コンパイルエラーが発生
}
}
interface InterfaceTest {
String method(String name);
}
このコードでは、`Main `クラスの` main `メソッド内で、`n `という変数を定義し、8 に初期化していますね。
`n` はこのラムダ式の中で使われていますが、ラムダ式の中や外で値が変更されると、コンパイルエラーになってしまいます。
ラムダ式が参照する変数は「final」または「事実上のfinal」の必要があるためです。
ラムダ式内で使用される外部変数(この例では n)は、変更されないことをインプットしておきましょう。
ラムダ式と例外処理
Javaのラムダ式では、チェック例外(プログラムで処理を強制される例外)をそのままスローできません。
ラムダ式で発生したチェック例外をその場で処理するか、ランタイム例外に変換してスローする必要があります。
ラムダ式で例外処理を記述した以下のサンプルコードを見てください。
List list = Arrays.asList("a", "b", "c");
try {
list.forEach(item -> {
if ("b".equals(item)) {
throw new RuntimeException("例外が発生しました");
}
System.out.println(item);
});
} catch (RuntimeException e) {
System.err.println("エラー発生: " + e.getMessage());
}
リストの各要素を `forEach` メソッドで処理していますよ。
ラムダ式内でif文を使い、` if (“b”.equals(item))` という条件を満たす要素(この場合は “b”)が見つかった場合に、RuntimeException をスローしています。
また、try {…} catch (RuntimeException e) {…} この部分で、ラムダ式内でスローされた例外を捕捉していますね。
ラムダ式で例外処理を行う際は、通常のメソッドとは異なるアプローチが必要です。
特に、ラムダ式内でチェック例外を扱う場合は、それをRuntimeExceptionに変換するか、ラムダ式の外で例外を捕捉する必要があります。
Javaプログラミングのスキルを活かして収入を増やすなら
なかには、副業での収入獲得やフリーランスへの独立を目的にJavaプログラミングのスキル習得に励んでいる人もいますよね。
ただ、身につけたスキルをどう収入UPに繋げればいいのか、イメージが湧かない人もいるはず。
そんな方は、ぜひフリーランスのミカタをご活用ください。
出典:フリーランスのミカタ
フリーランスのミカタは、平均単価80万円以上の案件を取り揃える、ITエンジニアに特化したフリーランスエージェントです。具体的には、次のような週3回からフルリモートで請け負える案件を豊富に掲載しています。
また希望年収や稼働時間だけでなく、扱うプログラミング言語などを細かく指定して案件を探せるため、自分にあう仕事を見つけやすいサイト仕様になっています。
ただし、上記のような案件は条件として2〜3年の実務経験が求められるケースが多いです。そのため、応募する際はどれくらいの経験が必要なのかを前もってチェックしておきましょう。
フリーランスのミカタを活用すれば、中・長期的な安定収入が得られる案件が見つかりますよ。
どんな案件が掲載されているか気になる人は、下のボタンから自分にあう案件を探してみてください。
[center][btn href=”https://freelance-mikata.com/ad/media_redirect_02/redirector?utm_source=ownedmedia&utm_medium=media&utm_campaign=lp” class=”cubic1 green-bc shadow”]公式サイトで詳細を見る[/btn][/center]
まとめ
これまで、ラムダ式の基本と使い方について紹介してきました。ラムダ式の主な活用法は、関数型インターフェースのインスタンスを簡単に作成し、プログラム内で関数として使用することです。
特に、Java 8から導入された「Stream API」は、ラムダ式を広範に活用し、配列やコレクションの要素を効率的に処理できます。
しかし、ラムダ式内で例外処理を行う場合や、外部変数を参照する際には特別な注意が必要です。
まだまだ知識が浅く、Javaの他の処理に関しても基礎知識をつけたい!応用的なJavaの書き方も学びたい!という方もいるでしょう。
本記事ではプログラミングに関する多くの疑問を解決できるので、参考にしてみてくださいね。