CodeGym /コース /JAVA 25 SELF /Method References (::): メソッド参照

Method References (::): メソッド参照

JAVA 25 SELF
レベル 21 , レッスン 1
使用可能

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::toUpperCasemap の中では正しく動作します。最初の引数が String 自身だからです。しかし、静的メソッドが期待される不適切な文脈で使うとエラーになります。

コメント
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION