1. はじめに
Method reference(メソッド参照)は、既存のメソッド(またはコンストラクタ)を関数型インターフェースの実装として渡せるようにする Java の特別な構文です。シグネチャが一致していれば、ラムダ式が期待される場所にメソッドを「渡す」ことができます。
構文:
Class::method
object::method
Class::new
ラムダが「その場で書く小さな関数」だとすれば、method reference は「既にあるメソッドをそのまま渡す」だけです。レシピを書き写す代わりに、そのレシピのページへのリンクを渡すようなものです。
まずは直感的な例
次のように書く代わりに:
list.forEach(s -> System.out.println(s));
こう書けます:
list.forEach(System.out::println);
ずっと簡潔ですね。
2. method reference の種類
メソッド参照には主要な 4 つの形があります。これだけ覚えれば十分です。
静的メソッドへの参照
構文: Class::staticMethod
例:
Function<Integer, String> intToString = String::valueOf;
System.out.println(intToString.apply(123)); // "123"
ラムダで書くと同じ:
Function<Integer, String> intToString = i -> String.valueOf(i);
インスタンス(オブジェクト)メソッドへの参照
構文: object::method
例:
PrintStream printer = System.out;
Consumer<String> consumer = printer::println;
consumer.accept("こんにちは、世界!");
ラムダと等価:
Consumer<String> consumer = s -> printer.println(s);
クラスの非静的メソッドへの参照
構文: Class::method
この形では、関数型インターフェースの最初の引数が、そのメソッドを呼び出す対象オブジェクトになります。
例:
Function<String, Integer> stringLength = String::length;
System.out.println(stringLength.apply("Java")); // 4
ここで String::length は次の関数に変換されます: (String s) -> s.length()
コンストラクタへの参照
構文: Class::new
例:
Supplier<ArrayList<String>> listSupplier = ArrayList::new;
ArrayList<String> list = listSupplier.get();
ラムダと等価:
Supplier<ArrayList<String>> listSupplier = () -> new ArrayList<>();
3. method reference をいつ使うべきか?
ラムダが単に既存メソッドの呼び出ししかしていないときは、method reference を使うとコードが短くなり、読みやすくなります。
例: リストのソート
次のように書く代わりに:
List<String> names = Arrays.asList("Ivan", "Petr", "Anna");
names.sort((a, b) -> a.compareToIgnoreCase(b));
こう書けます:
names.sort(String::compareToIgnoreCase);
例: コレクションの処理
次のように:
list.forEach(s -> System.out.println(s));
さらに短く書けます:
list.forEach(System.out::println);
例: 要素の変換
次のように:
List<String> numbers = Arrays.asList("1", "2", "3");
List<Integer> ints = numbers.stream()
.map(s -> Integer.parseInt(s))
.collect(Collectors.toList());
こう書けます:
List<Integer> ints = numbers.stream()
.map(Integer::parseInt)
.collect(Collectors.toList());
Stream API とユーティリティクラス Collectors の詳細は、レベル 30 で学びます :P
4. method reference とラムダ式の比較
同等性
method reference とラムダ式はしばしば相互に置き換え可能です。シグネチャが一致していれば、どちらも関数型インターフェースを実装します。
例:
Consumer<String> c1 = s -> System.out.println(s);
Consumer<String> c2 = System.out::println;
method reference を使うと良いケース
- ラムダが追加のロジックなしで既存メソッドを呼ぶだけのとき。
- 可読性を上げたいとき。特に長い呼び出しチェーンで有効。
- 「ここは単にメソッド呼び出しだ」と明示したいとき。
method reference が適さないケース
- 追加のロジック(検証・条件分岐・エラー処理等)が必要なとき。
- メソッドを呼ぶ前に引数を変換する必要があるとき。
例:
list.forEach(s -> {
if (s != null) System.out.println(s);
});
// ここは method reference では不適。ラムダが必要。
5. 実践: ラムダ式を method reference に書き換える
例 1: 動物名を出力する
List<String> animals = Arrays.asList("Cat", "Dog", "Parrot");
animals.forEach(animal -> System.out.println(animal));
こうなる:
animals.forEach(System.out::println);
例 2: 文字列を数値に変換する
List<String> numbers = Arrays.asList("10", "20", "30");
List<Integer> ints = numbers.stream()
.map(s -> Integer.parseInt(s))
.collect(Collectors.toList());
こうなる:
List<Integer> ints = numbers.stream()
.map(Integer::parseInt)
.collect(Collectors.toList());
例 3: 名前でオブジェクトをソートする
List<Animal> animalList = ...;
animalList.sort((a, b) -> a.getName().compareTo(b.getName()));
こうなる:
animalList.sort(Comparator.comparing(Animal::getName));
ここで Animal::getName はクラスの非静的メソッドへの参照です。
例 4: コンストラクタでオブジェクトを作る
Supplier<Dog> dogFactory = () -> new Dog();
Dog dog = dogFactory.get();
こうなる:
Supplier<Dog> dogFactory = Dog::new;
Dog dog = dogFactory.get();
6. シグネチャの対応の仕組み
method reference は、メソッドのシグネチャが関数型インターフェースの抽象メソッドと一致するときにだけ使えます。
@FunctionalInterface
interface IntToString {
String convert(int value);
}
public class Demo {
public static String intToHex(int value) {
return Integer.toHexString(value);
}
public static void main(String[] args) {
IntToString converter = Demo::intToHex;
System.out.println(converter.convert(255)); // ff
}
}
ここでは Demo::intToHex が適合します。int を受け取り、String を返すためです。
7. 引数付きコンストラクタと method reference
コンストラクタが引数を取る場合でも、シグネチャが一致していれば method reference を使えます。
@FunctionalInterface
interface AnimalFactory {
Animal create(String name);
}
class Animal {
private String name;
public Animal(String name) { this.name = name; }
public String getName() { return name; }
}
AnimalFactory factory = Animal::new;
Animal cat = factory.create("Barsik");
System.out.println(cat.getName()); // Barsik
8. method reference でよくある誤り
エラー 1: シグネチャ不一致。
メソッドのシグネチャがインターフェースの抽象メソッドと一致しない場合、コンパイラはエラーを出します。例えば、インターフェースは 2 つの引数を期待しているのに、参照先メソッドが 1 つの引数しか取らない場合などです。
エラー 2: クラスの非静的メソッド参照をオブジェクトなしで使おうとする。
Class::method の形では、インターフェースの最初の引数が呼び出し対象のオブジェクトになります。引数の数や順序を取り違えると、対応付けのエラーになります。
エラー 3: 追加ロジックが必要な場面で method reference を使う。
条件分岐、ロギング、例外処理などが必要な場合は、メソッド参照ではなくラムダを使いましょう。
エラー 4: オーバーロードされたメソッドへの参照。
同名メソッドが複数あるクラスでは、コンパイラがどれを選ぶべきか分からないことがあります。関数型インターフェースの型を明示すると解決する場合があります。
エラー 5: 非静的メソッド参照をオブジェクトなしで使う。
例えば、String::toUpperCase は map の中では正しく動作します。最初の引数が String 自身だからです。しかし、静的メソッドが期待される不適切な文脈で使うとエラーになります。
GO TO FULL VERSION