メソッド参照とは既に定義されているメソッドを記述し、staticなメソッドやインスタンスが持つメソッドを実行させる表記法になります。メソッド参照では引数が省略できる分、ラムダ式より記述量が減らすことが可能になります。また引数を省略している分、基本的には関数型インターフェースが受け取った引き数を、そのまま参照されているメソッドに渡すような場合に使われるのに適しています。
メソッド参照の表記法は下記のように{クラス名}/{インスタンス名}と{メソッド名を「::」(2つのコロン)でつなげたものです。
{クラス名}::{メソッド名}
{インスタンス名}::{メソッド名}
{クラス名}もしくは{インスタンス名}をどう使い分けるかというと、staticメソッドや関数型インターフェースに渡される引数のメソッドを使う場合は{クラス名}、生成されているインスタンスのメソッドを使う場合は{インスタンス名}になります。
また、「{インスタンス名}::{メソッド名}」の場合で、そのインスタンスが自分自身の場合、「this」を使って、下記のように記述します。
this::{メソッド名}
また、メソッド参照で記述する場合は引数を記述しません。引数はその関数型インターフェースの引数と同じ型で同じ個数を持つものと暗黙的に決まってしまいます。逆にいうと、関数型インターフェースの引数と違う型や違う個数のものは定義できません。
メソッド参照で使えるメソッドは次の3つのパターンがあります。
それでは、それぞれのパターンを見ていきましょう。
staticメソッドを参照する場合はクラス名を指定して参照するメソッドを定義します。
次のサンプルでは下部に定義されているstaticメソッドを参照しています。このstaticメソッドは受け取った値を標準出力するだけのメソッドになります。また、メソッド参照では自分自身にあるstaticメソッドを参照する場合でもクラス名は省略できません。
public class MethodReferenceSample1 {
public static void main(String[] args) {
List<String> list = Arrays.asList("A", "D", "B", "C", "E");
// staticメソッドを参照する場合
System.out.println("--- 匿名クラス ---");
list.stream().forEach( new Consumer<String>() {
@Override
public void accept(String value) {
MethodReferenceSample1.println(value);
}
});
System.out.println("--- ラムダ式 ---");
list.stream().forEach(value -> MethodReferenceSample1.println(value));
System.out.println("--- メソッド参照 ---");
list.stream().forEach(MethodReferenceSample1::println);
// list.stream().forEach(::println); // ← これはNG
// list.stream().forEach(println); // ← これはNG
}
private static void println(String value){
System.out.println(value);
}
}
--- 匿名クラス --- A D B C E --- ラムダ式 --- A D B C E --- メソッド参照 --- A D B C E
インスタンスのメソッドを参照する場合はインスタンス名もしくは自クラスの場合は「this」を指定して参照するメソッドを定義します。
このサンプルでは生成したインスタンスのメソッドを参照する例を見るために、下部に定義されているインナークラスのメソッドを参照しているものと、thisで 呼ばれるメソッドを参照する例を見るために、自クラスにあるメソッドを参照しているもののパターンをメソッド参照の例として提示しています。
また、System.out.printlnメソッドは、Systemのインスタンスであるoutが持つprintlnメソッドとなるので、これもメソッド参照で定義できます。
それではサンプルを見てみましょう。
public class MethodReferenceSample2 {
public static void main(String[] args) {
MethodReferenceSample2 sample = new MethodReferenceSample2();
sample.process();
}
private void process(){
List<String> list = Arrays.asList("A", "D", "B", "C", "E");
// インスタンスのメソッドを参照する場合
StringComparator comparator = new StringComparator();
System.out.println("--- ラムダ式 ---");
list.stream()
.sorted((value1, value2) -> comparator.compare(value1, value2))
.forEach((value) -> System.out.println(value));
System.out.println("--- メソッド参照 ローカル変数---");
list.stream()
.sorted(comparator::compare)
.forEach(System.out::println);
System.out.println("--- メソッド参照 this---");
list.stream()
.sorted(this::compareValues)
.forEach(System.out::println);
}
private class StringComparator implements Comparator<String> {
public int compare(String value1, String value2){
return compareValues(value1, value2);
}
}
public int compareValues(String value1, String value2){
return value1.compareTo(value2);
}
}
--- ラムダ式 --- A B C D E --- メソッド参照 ローカル変数--- A B C D E --- メソッド参照 this--- A B C D E
関数型インターフェースの引数のメソッドを参照する場合は、その引数のクラス名を定義します。
メソッド参照では引数の宣言が省略されるので分かりにくくなるのですが、今までのメソッド参照とは違い、ここでは関数型インターフェースの引数はそのままメソッドの引数にはなりません。
この関数型インターフェースの引数のメソッドをメソッド参照として使う場合は、第1引数となる要素がクラス名の部分で表しているものになり、メソッド名はその第1引数の要素が実行するメソッドを表すことになります。そのため、Stream#sortedメソッドのように2つの引数を持つものはクラス名が第1引数の要素となり、第2引数がメソッドの引数となります。
それでは、サンプルを見てみましょう。次のサンプルは要素の文字列を小文字に変換し並び替えたものを標準出力したものになります。
public class MethodReferenceSample3 {
public static void main(String[] args) {
List<String> list = Arrays.asList("A", "D", "B", "C", "E");
// 関数型インターフェースの引数のメソッドを参照する場合
System.out.println("--- ラムダ式 ---");
list.stream()
.map(value -> value.toLowerCase())
.sorted((value1, value2) -> value1.compareTo(value2))
.forEach(System.out::println);
System.out.println("--- メソッド参照 ---");
list.stream()
.map(String::toLowerCase)
.sorted(String::compareTo)
.forEach(System.out::println);
}
}
ここでは、mapメソッドで定義しているメソッド参照は、受け取った要素の値を小文字にしています。そして、sortedメソッドで第1引数としてくるStringの要素を、その要素が持つcompareToメソッドで第2引数の要素と比較して結果を返しています。最後にforEachメソッドで各要素を標準出力しています。
Copyright © ITmedia, Inc. All Rights Reserved.