インスタンスメソッド参照は次のように記述する。
// インスタンスが指定できる場合
インスタンス::メソッド名
// インスタンスが指定できない場合
クラス名::メソッド名
インスタンスメソッドに対するメソッド参照はインスタンスを直接指定できるかどうかで、記述が異なる。
インスタンスが指定できる場合はインスタンスを記述して、コロンの後メソッド名を記述する。一方のインスタンスが指定できない場合、インスタンスの代わりにクラス名を使用する。
次の例はリストに保持させた文字列を連結している。
List<String> texts = ... ;
StringBuilder builder = new StringBuilder();
// リストが保持する文字列の連結
texts.forEach(text -> builder.append(text));
System.out.println(builder.toString());
これはラムダ式は以下のメソッド参照に置き換えることができる。
texts.forEach(builder::append);
この場合もクラスメソッド参照と同様に、引数の並びは関数型インタフェースとメソッド参照で一致しなくてはならない。
ラムダ式でローカル変数を使用する場合、finalもしくは実質的finalである必要があるが、メソッド参照の場合、参照するローカル変数はfinalである必要 がない。
List<String> texts = ... ;
StringBuilder builder = new StringBuilder();
// builderに再代入したため、実質的finalではなくなる
builder = new StringBuilder(initialText);
// builderが実質的finalでないためNG
texts.forEach(text -> builder.append(text));
// メソッド参照の場合、実質的finalでなくてもOK
texts.forEach(builder::append);
複数の引数がある場合でも使うことができる。
次に示すコードは、ラムダ式でTaskクラスのexecuteメソッドをコールしています。
public class MethodReferenceDemo {
class Task {
public void execute(T t) {
// タスク処理
}
}
@FunctionalInterface
interface TwoArgFunction {
void apply(S s, T t);
}
public MethodReferenceDemo() {
// Taskクラスのexecuteメソッドをコールするラムダ式
TwoArgFunction function = (task, text) -> task.execute(text);
function.apply(new Task<>(), Hello, World!);
}
public static void main(String... args) {
new MethodReferenceDemo();
}
}
メソッド参照を使用するにあたって、同じ名前のクラスメソッドとインスタンスメソッドは注意が必要となる。
たとえば、DoubleクラスのhashCodeメソッドにメソッド参照を使ってみる。
@FunctionalInterface
interface Function<T> {
void apply(T t);
}
public MethodReferenceDemo() {
// NG staticメソッドのDouble.hashCode(double)と
// インスタンスメソッドのDouble#hashCode()メソッドの
// どちらを指しているかあいまいであるため
Function<Double> func = Double::hashCode;
}
DoubleクラスのhashCodeメソッドはインスタンスメソッドのhashCode()と、staticメソッドのhashCode(double)の2種類があります。このため、Double::hashCodeというメソッド参照がどちらのメソッドをコールすればいいのか一意に決まらず、コンパイルエラーとなる。
このような場合はラムダ式で記述する。
メソッド参照でチェックされる例外をスローするメソッドを記述した場合、コンパイルエラーになる。
List<Path> paths = ...;
// NG メソッド参照ではチェックド例外のtry catchができない
paths.forEach(Files::delete);
FilesクラスのdeleteメソッドはIOException例外をスローする。
しかし、メソッド参照を使用している場合、IOException例外のtry catchを記述することができない。
このようにチェックド例外をスローする場合は、ラムダ式を使用し、ラムダ式内で例外処理を行う必要がある。
List<Path> paths = ...;
paths.forEach(path -> {
try {
Files.delete(path);
} catch (IOException ex) {
// 例外処理
}
});