【Java】Javaのメソッド参照の使い方を4パターンで解説

Java

こんにちは、かつコーチです。
s -> s.length()のようなラムダ式をあちこちで見かける一方、String::lengthという書き方に戸惑ったことはありませんか。
今回はJavaのメソッド参照(既存のメソッドをそのままラムダ式代わりに使う書き方)を4つのパターンに分けて解説します。

メソッド参照とは?

ラムダ式との関係

メソッド参照はラムダ式の糖衣構文(読みやすく書き換えた別の書き方)で、「既にあるメソッドを呼ぶだけ」のラムダ式をより短く書くための仕組みです。

import java.util.List;

List<String> names = List.of("かつコーチ", "Aさん", "Bさん");

// ラムダ式
names.forEach(name -> System.out.println(name));

// メソッド参照
names.forEach(System.out::println);

name -> System.out.println(name)は「受け取った引数をそのままprintlnに渡すだけ」の処理です。
このような「引数をそのまま渡すだけ」のラムダ式は、メソッド参照クラス名::メソッド名に書き換えられます。

基本の書き方:4つのパターン

1. 静的メソッド参照

import java.util.List;
import java.util.function.Function;

Function<String, Integer> parse = Integer::parseInt;
System.out.println(parse.apply("123")); // 123

クラス名::静的メソッド名の形式で、s -> Integer.parseInt(s)と同じ意味です。

2. 特定インスタンスのメソッド参照

String prefix = "こんにちは、";
Function<String, String> greet = prefix::concat;

System.out.println(greet.apply("かつコーチさん")); // こんにちは、かつコーチさん

すでに存在するインスタンスに対してインスタンス変数::メソッド名と書く形式で、s -> prefix.concat(s)と同じ意味です。

3. 不特定インスタンスのメソッド参照

import java.util.List;
import java.util.function.Function;

List<String> names = List.of("かつコーチ", "Aさん");

Function<String, Integer> toLength = String::length;
System.out.println(toLength.apply(names.get(0))); // 5

クラス名::インスタンスメソッド名の形式で、s -> s.length()と同じ意味です。
引数として渡された値自身がメソッドの呼び出し元になる点が、2番目のパターンとの違いです。

4. コンストラクタ参照

import java.util.function.Supplier;
import java.util.ArrayList;
import java.util.List;

Supplier<List<String>> listFactory = ArrayList::new;
List<String> newList = listFactory.get();
newList.add("かつコーチ");

System.out.println(newList); // [かつコーチ]

クラス名::newの形式で、() -> new ArrayList<>()と同じ意味です。
Stream APIのcollectで独自のコンテナに詰め替えたいときなどに使います。

よくあるつまずきポイント・エラー対処

3番目と2番目のパターンを混同してコンパイルエラーになった

実際に私が最初に混乱したのは、「不特定インスタンス」と「特定インスタンス」のどちらのパターンか判断できず、引数の数を合わせられなかったケースです。

// ❌Before:BiFunctionのつもりでFunctionを使ってしまう
import java.util.function.Function;

// String::compareToは本来 (自分自身, 引数) の2つのStringを必要とする
Function<String, Integer> compare = String::compareTo; // コンパイルエラー

compareTos1.compareTo(s2)のように呼び出し元と引数の両方が必要なメソッドなので、引数を1つしか受け取らないFunction<String, Integer>には代入できません。

// ✅After:BiFunctionを使う(2つの引数を受け取る型)
import java.util.function.BiFunction;

BiFunction<String, String, Integer> compare = String::compareTo;
System.out.println(compare.apply("apple", "banana")); // 負の数

「不特定インスタンスのメソッド参照」では、呼び出し元になるはずの1つ目の引数もメソッドの引数として数える必要があります。
メソッド参照を書くときは「元のメソッドが何個の引数を必要とするか」を先に数え、それに合った関数型インターフェースを選ぶと迷いません。

応用・一歩先の使い方

Stream APIとの組み合わせ

import java.util.List;
import java.util.stream.Collectors;

record Product(String name, int price) {}

List<Product> products = List.of(
        new Product("キーボード", 8000),
        new Product("マウス", 3000)
);

List<String> names = products.stream()
        .map(Product::name) // 不特定インスタンスのメソッド参照
        .collect(Collectors.toList());

System.out.println(names); // [キーボード, マウス]

map(Product::name)のように、Stream APIの中間操作・終端操作ではメソッド参照が頻繁に登場します。
ラムダ式でp -> p.name()と書くよりも短く、意図が伝わりやすいコードになります。

ラムダ式とメソッド参照の使い分け

「既存のメソッドをそのまま呼ぶだけ」ならメソッド参照、「条件分岐や複数行の処理が必要」ならラムダ式、というのが基本の判断軸です。
無理にすべてをメソッド参照に置き換えると逆に読みにくくなることもあるため、可読性を優先して選ぶことをおすすめします。

まとめ

この記事のポイント

  • メソッド参照は「既存のメソッドをそのまま使うだけ」のラムダ式を短く書くための糖衣構文
  • 静的メソッド・特定インスタンス・不特定インスタンス・コンストラクタの4パターンがある
  • 不特定インスタンスのメソッド参照では、呼び出し元自身も引数の1つとして数える
  • Stream APIのmapfilterとの相性がよく、map(Product::name)のように使われる

次に読むべき記事

  • ラムダ式の基本
  • 関数型インターフェース(Function・Predicate等)

タグ: Java, 中級者向け, 関数型プログラミング

タイトルとURLをコピーしました