メソッド参照とJava8以降の新機能
📋 この記事の目次
前回、TicketCraftのアクションをConsumerとして部品化しました。改めてコードを見直すと、あることに気づきます。
Consumer<Ticket> logger = ticket -> auditLog.record(ticket);
これは「引数ticketを受け取って、そのままauditLog.record(ticket)を呼んでいるだけ」です。既存のメソッドをそのまま呼ぶだけなら、もっと短く書けます。今回はメソッド参照で、この手のラムダ式を簡潔にします。
メソッド参照は「マニュアルのページ番号を指すだけ」だと考える
「このケースはマニュアルの5ページを見てください」と伝えるだけで済むなら、わざわざ内容を書き写す必要はありません。メソッド参照とは、まさに 「既存のメソッドをそのまま指し示すだけ」 の書き方です。::という演算子を使います。
実践:4つのメソッド参照パターン
Step 1: 静的メソッド参照
List<Ticket> tickets = getAllTickets();
tickets.forEach(TicketPrinter::print);
TicketPrinterクラスのstaticメソッドprintをそのまま渡しています。ラムダ式で書くとticket -> TicketPrinter.print(ticket)と同じ意味です。
Step 2: 特定インスタンスのメソッド参照
AuditLog auditLog = new AuditLog();
Consumer<Ticket> logger = auditLog::record;
すでに生成済みのauditLogインスタンスのrecordメソッドを参照しています。冒頭のticket -> auditLog.record(ticket)と同じ意味です。
Step 3: 任意のインスタンスのメソッド参照
tickets.sort(Comparator.comparing(Ticket::getDueDate));
Ticket::getDueDateは、「(引数として渡されてくる)任意のTicketインスタンスのgetDueDateメソッドを呼ぶ」という意味です。ラムダ式で書くとticket -> ticket.getDueDate()と同じ意味になります。
Step 4: コンストラクタ参照
Supplier<TicketDraft> draftFactory = TicketDraft::new;
TicketDraft draft = draftFactory.get();
TicketDraft::newは「TicketDraftのコンストラクタを参照する」という意味です。() -> new TicketDraft()と同じ意味になります。
使い分け早見表
| パターン | 書き方 | 意味 |
|---|---|---|
| 静的メソッド参照 | クラス名::静的メソッド名 |
staticメソッドを参照する |
| 特定インスタンスのメソッド参照 | 変数名::メソッド名 |
生成済みインスタンスのメソッドを参照する |
| 任意のインスタンスのメソッド参照 | クラス名::インスタンスメソッド名 |
引数で渡されるインスタンスのメソッドを参照する |
| コンストラクタ参照 | クラス名::new |
コンストラクタを参照する |
メソッド参照が使えない場面
引数を加工したり、複数の処理を組み合わせたりする場合は、素直にラムダ式を使います。
// これはメソッド参照にできない(加工が入っているため)
tickets.forEach(ticket -> TicketPrinter.print("[" + ticket.getStatus() + "] " + ticket.getTitle()));
「既存のメソッド呼び出しをそのまま渡しているだけか」を基準に、メソッド参照とラムダ式を使い分けましょう。
🧓 ベテランの現場コラム メソッド参照は書けば書くほど「モダンなコードを書けている」気分になりますが、無理に全部をメソッド参照に統一しようとすると、型推論が効きにくい場面でかえって読みにくくなることがあります。「シンプルに既存メソッドを呼ぶだけ」の場合だけメソッド参照にする、という線引きを持っておくと迷いません。
Java8以降の主な新機能ダイジェスト
ここまで扱ったラムダ式・Stream API・関数型インターフェース・メソッド参照は、いずれもJava8で追加された機能です。Java8以降も、Javaは継続的に進化しています。この連載では深追いしませんが、存在だけ押さえておくと良いものを挙げておきます。
| バージョン | 主な新機能 | 一言で言うと |
|---|---|---|
| Java 8 | java.timeパッケージ、Optional |
日付・時刻の扱いやすさ向上、nullを安全に扱う仕組み |
| Java 10 | ローカル変数型推論(var) |
型を書かなくても推論してくれる |
| Java 16 | recordクラス |
データ保持用クラスを1行で定義できる |
| Java 17 | シールドクラス | 継承できるクラスを制限し、設計意図を明確にできる |
| Java 21 | 仮想スレッド | 軽量スレッドによる高い並行処理性能 |
TicketCraftのBugTicket・FeatureTicketのような単純にデータを保持するクラスは、将来的にはrecordで書き直すことも検討できますが、まずは今回までのOOP基礎編・本編の内容を土台にすることを優先しましょう。
レガシー環境での互換性
もしTicketCraftの一部が、何らかの理由で古いJavaバージョン(Java 7以前)でも動かす必要が出てきた場合、ラムダ式やメソッド参照は使えません。Comparatorは匿名クラスで、Stream風の処理は素直なfor文で書き直すことになります。
// Java7以前互換の書き方(匿名クラス)
Collections.sort(tickets, new Comparator<Ticket>() {
@Override
public int compare(Ticket t1, Ticket t2) {
return t1.getDueDate().compareTo(t2.getDueDate());
}
});
普段はモダンな書き方で構いませんが、「なぜこの書き方が必要なのか」を知っておくと、レガシーシステムの保守を任されたときにも慌てずに済みます。
🧓 ベテランの現場コラム 新人の頃、古いJavaバージョンで動いている社内システムの保守を任され、「ラムダ式が使えない…」と戸惑ったことがあります。モダンな書き方だけを覚えていると、こうした場面で手が止まってしまいます。今回のような「モダンな書き方は、そもそも何を簡潔にしたものなのか」を理解しておけば、書き方を戻すこと自体はそれほど難しくありません。
挫折ポイントTOP3と対処法
1. 4パターンの区別に迷う
「staticメソッドか」「すでにあるインスタンスか」「引数で渡されるインスタンスか」「コンストラクタか」の4択で機械的に判断しましょう。
2. 無理に全部メソッド参照に書き換えようとする
加工が必要な処理は、素直にラムダ式のままにしておいて構いません。
3. Java8以降の新機能を一度に全部覚えようとする
存在を知っておくだけで十分です。実際に使う場面が来たときに、改めて詳しく調べれば間に合います。
この先、何を学べばいいか
Java8モダン文法編はこれで完結です。次回からは設計編として、TicketCraftの通知処理を題材にDI(依存性注入)とAOP(アスペクト指向プログラミング)を学び、Springフレームワークでの実践に踏み込みます。
まとめ
- メソッド参照は「既存のメソッドをそのまま指し示す」ための省略記法(
::) - 静的メソッド・特定インスタンス・任意のインスタンス・コンストラクタの4パターンがある
- 加工が必要な処理には無理に使わず、ラムダ式のままでよい
- Java8以降の新機能は、まず存在を知っておくだけで十分。必要になったら都度調べる
🎓 理解度テスト
学んだ内容を確認しましょう。全5問。