- JAVA
Javaメソッド参照(::)の使い方|4種類とラムダ対応表
メソッド参照とは、「すでにあるメソッドを呼ぶだけ」のラムダ式を、クラス名::メソッド名 の形で短く書く記法です。 s -> s.length() は String::length、s -> Integer.parseInt(s) は Integer::parseInt と書けます。
この記事では、メソッド参照の4種類と配列コンストラクタ参照を「書き方・等価なラムダ・合う関数型インターフェース」の対応表にまとめ、すべて動かした結果を載せています。あわせて、Integer::toString や String::valueOf で出る「参照はあいまいです」のエラーの再現と直し方、メソッド参照はその場で評価され、ラムダは呼び出し時に評価されるという違いを、null で例外が出る時点の差で確かめます。
確認環境:Ubuntu 24.04 / OpenJDK 21.0.10(java・javac)。コンパイルエラーは javac -J-Duser.language=ja で日本語のメッセージを表示しています。
メソッド参照の早見表(4種類+配列)
| 種類 | 書き方 | 例 | 等価なラムダ | 合う関数型インターフェースの例 |
|---|---|---|---|---|
| 1. static メソッド参照 | クラス名::staticメソッド |
Integer::parseInt |
s -> Integer.parseInt(s) |
Function<String, Integer> |
| 2. 特定のオブジェクトのインスタンスメソッド参照 | 変数::メソッド |
prefix::concat |
name -> prefix.concat(name) |
Function<String, String> |
| 3. 任意のオブジェクトのインスタンスメソッド参照 | クラス名::インスタンスメソッド |
String::length |
s -> s.length() |
Function<String, Integer> |
| 3'. 同上(引数が2つ) | クラス名::インスタンスメソッド |
String::equalsIgnoreCase |
(a, b) -> a.equalsIgnoreCase(b) |
BiFunction<String, String, Boolean> |
| 4. コンストラクタ参照 | クラス名::new |
ArrayList::new |
() -> new ArrayList<>() |
Supplier<List<String>> |
| 5. 配列コンストラクタ参照 | 型[]::new |
int[]::new |
n -> new int[n] |
IntFunction<int[]> |
4種類という分け方は、Oracle の Java チュートリアル(Method References)の表「Kinds of Method References」に沿っています。配列コンストラクタ参照は、Java 言語仕様 15.13 の構文に ArrayType :: new として載っている形です。
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「Javaメソッド参照(::)の使い方|4種類とラムダ対応表」
4種類と配列コンストラクタ参照を動かす
早見表の例を、ラムダとメソッド参照の両方で書いて結果を比べます。
import java.util.*;
import java.util.function.*;
import java.util.stream.*;
public class Kinds {
public static void main(String[] args) {
// 1. static メソッド参照: クラス名::staticメソッド
Function<String, Integer> parse1 = s -> Integer.parseInt(s);
Function<String, Integer> parse2 = Integer::parseInt;
System.out.println("1. " + parse1.apply("42") + " / " + parse2.apply("42"));
// 2. 特定のオブジェクトのインスタンスメソッド参照: 変数::メソッド
String prefix = "Hello, ";
Function<String, String> greet1 = name -> prefix.concat(name);
Function<String, String> greet2 = prefix::concat;
System.out.println("2. " + greet1.apply("Taro") + " / " + greet2.apply("Taro"));
// 3. 任意のオブジェクトのインスタンスメソッド参照: クラス名::インスタンスメソッド
// 第1引数がレシーバー(メソッドを呼ばれる側)になる
Function<String, Integer> len1 = s -> s.length();
Function<String, Integer> len2 = String::length;
BiFunction<String, String, Boolean> eq1 = (a, b) -> a.equalsIgnoreCase(b);
BiFunction<String, String, Boolean> eq2 = String::equalsIgnoreCase;
System.out.println("3. " + len1.apply("apple") + " / " + len2.apply("apple")
+ " / " + eq1.apply("Java", "JAVA") + " / " + eq2.apply("Java", "JAVA"));
// 4. コンストラクタ参照: クラス名::new
Supplier<List<String>> list1 = () -> new ArrayList<>();
Supplier<List<String>> list2 = ArrayList::new;
Function<String, StringBuilder> sb1 = s -> new StringBuilder(s);
Function<String, StringBuilder> sb2 = StringBuilder::new;
System.out.println("4. " + list1.get() + " / " + list2.get()
+ " / " + sb1.apply("ab").reverse() + " / " + sb2.apply("ab").reverse());
// 5. 配列コンストラクタ参照: 型[]::new
IntFunction<int[]> arr1 = n -> new int[n];
IntFunction<int[]> arr2 = int[]::new;
String[] names = Stream.of("b", "a", "c").sorted().toArray(String[]::new);
System.out.println("5. " + arr1.apply(3).length + " / " + Arrays.toString(arr2.apply(3))
+ " / " + Arrays.toString(names));
}
}
java Kinds.java
実行結果:
1. 42 / 42
2. Hello, Taro / Hello, Taro
3. 5 / 5 / true / true
4. [] / [] / ba / ba
5. 3 / [0, 0, 0] / [a, b, c]
どの行も、ラムダとメソッド参照で同じ結果になっています。
2番と3番の見分け方::: の左が「変数」か「クラス名」か
つまずきやすいのは、2番と3番の違いです。
| 種類 | :: の左 | メソッドを呼ばれる相手(レシーバー) | 関数の引数 |
|---|---|---|---|
2. prefix::concat |
変数(オブジェクト) | その変数に入っているオブジェクト。作った時点で決まる | すべてメソッドの引数になる |
3. String::concat |
クラス名 | 関数の第1引数 | 第2引数以降がメソッドの引数になる |
prefix::concat は「prefix に対して concat を呼ぶ」なので、関数の引数は1つ(つなげる文字列)です。String::concat は「渡された1つ目の文字列に対して、2つ目を concat する」なので、関数の引数は2つになります。クラス名::インスタンスメソッドの形では、関数の引数がメソッドの引数より1つ多いと覚えると整理できます。
1番と3番は書き方が同じ
Integer::parseInt(static)と String::length(インスタンス)は、どちらも クラス名::メソッド名 です。コンパイラは、そのメソッドが static かどうかと、受け取る関数型インターフェースの引数の数から、どちらの意味かを判断します。この仕組みが、後半の「参照はあいまいです」というエラーの理由になります。
ラムダからメソッド参照への書き換え対応表
Stream でよく書くラムダを、メソッド参照に書き換えた対応表です。
| やりたいこと | ラムダ | メソッド参照 | 種類 |
|---|---|---|---|
| null を除く | filter(s -> s != null) |
filter(Objects::nonNull) |
1 |
| 前後の空白を取る | map(s -> s.trim()) |
map(String::trim) |
3 |
| 空文字を除く | filter(s -> !s.isBlank()) |
filter(Predicate.not(String::isBlank)) |
3 |
| 大文字小文字を無視して並べる | sorted((a, b) -> a.compareToIgnoreCase(b)) |
sorted(String::compareToIgnoreCase) |
3' |
| 項目を取り出す | map(i -> i.name()) |
map(Item::name) |
3 |
| 項目で並べる | sorted((a, b) -> Integer.compare(a.stock(), b.stock())) |
sorted(Comparator.comparingInt(Item::stock)) |
3 |
| 合計する | reduce(0, (a, b) -> a + b) |
reduce(0, Integer::sum) |
1 |
| 最大を求める | reduce((a, b) -> Math.max(a, b)) |
reduce(Math::max) |
1 |
| Map にする | toMap(i -> i.name(), i -> i.stock()) |
toMap(Item::name, Item::stock) |
3 |
| 文字列にする | map(i -> String.valueOf(i.stock())) |
map(Item::stock).map(String::valueOf) |
3 と 1 |
| 1件ずつ出力する | forEach(s -> System.out.println(s)) |
forEach(System.out::println) |
2 |
表の左右が同じ結果になることを、次のコードで確かめました。各行の先頭の true は、ラムダ版とメソッド参照版の結果が等しいことを表します。
import java.util.*;
import java.util.function.*;
import java.util.stream.*;
public class Idioms {
record Item(String name, int stock) {}
public static void main(String[] args) {
List<String> words = Arrays.asList(" apple ", "kiwi", null, "", "Banana");
List<Item> items = List.of(new Item("ペン", 30), new Item("ノート", 12), new Item("付箋", 0));
// 左がラムダ、右がメソッド参照。結果が同じなら true
check("null を除く", words.stream().filter(s -> s != null).toList(),
words.stream().filter(Objects::nonNull).toList());
List<String> nonNull = words.stream().filter(Objects::nonNull).toList();
check("前後の空白を取る", nonNull.stream().map(s -> s.trim()).toList(),
nonNull.stream().map(String::trim).toList());
check("空文字を除く", nonNull.stream().filter(s -> !s.isBlank()).toList(),
nonNull.stream().filter(Predicate.not(String::isBlank)).toList());
check("大文字小文字を無視して並べる", nonNull.stream().sorted((a, b) -> a.compareToIgnoreCase(b)).toList(),
nonNull.stream().sorted(String::compareToIgnoreCase).toList());
check("項目を取り出す", items.stream().map(i -> i.name()).toList(),
items.stream().map(Item::name).toList());
check("項目で並べる", items.stream().sorted((a, b) -> Integer.compare(a.stock(), b.stock())).toList(),
items.stream().sorted(Comparator.comparingInt(Item::stock)).toList());
check("合計する", items.stream().map(i -> i.stock()).reduce(0, (a, b) -> a + b),
items.stream().map(Item::stock).reduce(0, Integer::sum));
check("int にして合計", items.stream().mapToInt(i -> i.stock()).sum(),
items.stream().mapToInt(Item::stock).sum());
check("最大を求める", items.stream().map(i -> i.stock()).reduce((a, b) -> Math.max(a, b)),
items.stream().map(Item::stock).reduce(Math::max));
check("Map にする", items.stream().collect(Collectors.toMap(i -> i.name(), i -> i.stock())),
items.stream().collect(Collectors.toMap(Item::name, Item::stock)));
check("文字列にする", items.stream().map(i -> String.valueOf(i.stock())).toList(),
items.stream().map(Item::stock).map(String::valueOf).toList());
// 出力するだけの処理
items.stream().map(Item::name).forEach(System.out::println);
}
static void check(String title, Object byLambda, Object byRef) {
System.out.println(byLambda.equals(byRef) + " " + title + " → " + byRef);
}
}
実行結果:
true null を除く → [ apple , kiwi, , Banana]
true 前後の空白を取る → [apple, kiwi, , Banana]
true 空文字を除く → [ apple , kiwi, Banana]
true 大文字小文字を無視して並べる → [, apple , Banana, kiwi]
true 項目を取り出す → [ペン, ノート, 付箋]
true 項目で並べる → [Item[name=付箋, stock=0], Item[name=ノート, stock=12], Item[name=ペン, stock=30]]
true 合計する → 42
true int にして合計 → 42
true 最大を求める → Optional[30]
true Map にする → {ノート=12, 付箋=0, ペン=30}
true 文字列にする → [30, 12, 0]
ペン
ノート
付箋
!s.isBlank() のように否定が入るラムダは、そのままではメソッド参照にできません。Predicate.not(String::isBlank) で包むと書けます。引数を加工してから渡す、2つのメソッドを続けて呼ぶ、といったラムダはメソッド参照にならないので、その場合はラムダのまま書くか、map(Item::stock).map(String::valueOf) のように段階を分けます。
コンストラクタ参照と this・super
コンストラクタ参照は、受け取る関数型インターフェースの引数に合わせて、呼ばれるコンストラクタが決まります。
import java.util.*;
import java.util.function.*;
import java.util.stream.*;
public class Ctor {
record Item(String name, int stock) {
Item() { this("(名前なし)", 0); }
Item(String name) { this(name, 0); }
}
static class Base {
String describe() { return "Base"; }
}
static class Child extends Base {
@Override String describe() { return "Child"; }
String tag(String s) { return "[" + s + "]"; }
void show() {
Supplier<String> mine = this::describe; // 自分のメソッド
Supplier<String> parent = super::describe; // 親クラスのメソッド
Function<String, String> tagger = this::tag;
System.out.println("this::describe=" + mine.get() + " / super::describe=" + parent.get() + " / this::tag=" + tagger.apply("x"));
}
}
public static void main(String[] args) {
// 同じ Item::new でも、受け取る型で呼ばれるコンストラクタが変わる
Supplier<Item> c0 = Item::new;
Function<String, Item> c1 = Item::new;
BiFunction<String, Integer, Item> c2 = Item::new;
System.out.println(c0.get());
System.out.println(c1.apply("ペン"));
System.out.println(c2.apply("ノート", 12));
// Stream と組み合わせる
List<Item> items = Stream.of("ペン", "ノート").map(Item::new).toList();
System.out.println(items);
Item[] array = items.stream().toArray(Item[]::new);
System.out.println(array.length + "件の配列 / 型: " + array.getClass().getSimpleName());
// コレクションを指定して集める
TreeSet<String> set = Stream.of("b", "a", "b").collect(Collectors.toCollection(TreeSet::new));
Map<Integer, List<String>> byLength = new HashMap<>();
for (String s : List.of("ab", "cd", "efg")) {
byLength.computeIfAbsent(s.length(), ArrayList::new).add(s);
}
System.out.println(set + " / " + byLength);
new Child().show();
}
}
実行結果:
Item[name=(名前なし), stock=0]
Item[name=ペン, stock=0]
Item[name=ノート, stock=12]
[Item[name=ペン, stock=0], Item[name=ノート, stock=0]]
2件の配列 / 型: Item[]
[a, b] / {2=[ab, cd], 3=[efg]}
this::describe=Child / super::describe=Base / this::tag=[x]
| 書き方 | 受け取る型 | 呼ばれるもの |
|---|---|---|
Item::new |
Supplier<Item> |
引数なしのコンストラクタ |
Item::new |
Function<String, Item> |
引数1つのコンストラクタ |
Item::new |
BiFunction<String, Integer, Item> |
引数2つのコンストラクタ |
Item[]::new |
IntFunction<Item[]> |
指定した長さの配列を作る |
this::describe |
Supplier<String> |
自分のメソッド(オーバーライド後) |
super::describe |
Supplier<String> |
親クラスのメソッド |
stream.toArray(Item[]::new) と書くと、Object の配列ではなく Item の配列として受け取れます。出力でも型が Item[] になっています。
computeIfAbsent に ArrayList::new を渡すときの動き
上のコードの byLength.computeIfAbsent(s.length(), ArrayList::new) は正しく動いていますが、1点知っておきたいことがあります。computeIfAbsent は「キーを受け取って値を返す関数」を求めるので、ArrayList::new は キー(ここでは Integer)を引数に取るコンストラクタ、つまり初期容量を指定する ArrayList(int) として解決されます。キーが小さな正の整数ならそのまま動きます。試したところ、キーが -1 のときは実行時に IllegalArgumentException: Illegal Capacity: -1 になり、キーが String の Map では「コンストラクタ参照が無効です」というコンパイルエラーになりました。どちらも k -> new ArrayList<>() とラムダで書くと、意図どおり空のリストが作られます。
メソッド参照とラムダの違い:評価される時点
見た目が短くなるだけでなく、動きが変わる場面が1つあります。:: の左側に書いた式が、いつ評価されるかです。
import java.util.function.*;
public class Timing {
static String name = "apple";
static String label() {
System.out.println(" (label() が呼ばれた)");
return "ID-";
}
public static void main(String[] args) {
// ---- 実験1:null のときに例外が出る時点 ----
String text = null;
System.out.println("1-a. ラムダを作る");
Supplier<Integer> lambda = () -> text.length();
System.out.println(" 作成できた。get() を呼ぶ");
try {
lambda.get();
} catch (NullPointerException e) {
System.out.println(" get() で NullPointerException");
}
System.out.println("1-b. メソッド参照を作る");
try {
Supplier<Integer> ref = text::length;
System.out.println(" 作成できた");
ref.get();
} catch (NullPointerException e) {
System.out.println(" 作成した時点で NullPointerException");
}
// ---- 実験2::: の左側の式が評価される回数 ----
System.out.println("2-a. ラムダ");
Function<String, String> f1 = s -> label().concat(s);
System.out.println(" 作成後。2回呼ぶ");
System.out.println(" " + f1.apply("1") + " " + f1.apply("2"));
System.out.println("2-b. メソッド参照");
Function<String, String> f2 = label()::concat;
System.out.println(" 作成後。2回呼ぶ");
System.out.println(" " + f2.apply("1") + " " + f2.apply("2"));
// ---- 実験3:作成後にフィールドを書き換える ----
Supplier<Integer> byLambda = () -> name.length();
Supplier<Integer> byRef = name::length;
name = "watermelon";
System.out.println("3. ラムダ: " + byLambda.get() + " / メソッド参照: " + byRef.get());
}
}
実行結果:
1-a. ラムダを作る
作成できた。get() を呼ぶ
get() で NullPointerException
1-b. メソッド参照を作る
作成した時点で NullPointerException
2-a. ラムダ
作成後。2回呼ぶ
(label() が呼ばれた)
(label() が呼ばれた)
ID-1 ID-2
2-b. メソッド参照
(label() が呼ばれた)
作成後。2回呼ぶ
ID-1 ID-2
3. ラムダ: 10 / メソッド参照: 5
3つの実験からわかることをまとめます。
| 実験 | ラムダ | メソッド参照(変数::メソッド) |
|---|---|---|
| 1. 変数が null | 作成はできる。呼び出した時点で NullPointerException | 作成した時点で NullPointerException |
| 2. :: の左がメソッド呼び出し | 呼び出すたびに label() が実行される(2回) | 作成時に1回だけ label() が実行される |
| 3. 作成後にフィールドを書き換え | 呼び出し時の値(watermelon の10文字)を見る | 作成時の値(apple の5文字)を覚えている |
Java 言語仕様 15.13.3 には、式::メソッド の形のメソッド参照では、まず :: の左側の式が評価され、その結果が null なら NullPointerException になる、と書かれています。ラムダの本体は、呼び出されるまで実行されません。
どちらを選ぶかの目安は次のとおりです。
| 状況 | 向いている書き方 | 理由 |
|---|---|---|
| 対象のオブジェクトが決まっていて変わらない | メソッド参照 | 作成時に null を検知でき、早く気づける |
| 呼び出すたびに最新の値を見たい | ラムダ | 呼び出し時にフィールドや式を評価する |
| 対象がまだ null で、後から設定される | ラムダ | 作成時には評価されない |
| :: の左が重い処理(生成や検索) | メソッド参照 | 1回だけ実行される |
なお、String::length のような「クラス名::メソッド」の形(1番・3番・4番)は、:: の左がクラス名なので、作成時に評価される式がありません。この違いが出るのは、2番の「変数::メソッド」「式::メソッド」の形です。
「参照はあいまいです」を再現して直す
メソッド参照でよく出会うコンパイルエラーが「参照はあいまいです」です。2つのパターンがあります。
パターン1:static とインスタンスの両方に当てはまる(Integer::toString)
import java.util.function.*;
public class A1 {
public static void main(String[] args) {
Function<Integer, String> f = Integer::toString;
}
}
A1.java:5: エラー: 不適合な型: メソッド参照が無効です
Function<Integer, String> f = Integer::toString;
^
toStringの参照はあいまいです
Integerのメソッド toString(int)とIntegerのメソッド toString()の両方が一致します
エラー1個
Integer クラスには、引数なしのインスタンスメソッド toString() と、int を1つ取る static メソッド toString(int) があります。Function<Integer, String> に対して Integer::toString と書くと、次の2通りに読めてしまいます。
| 読み方 | 等価なラムダ | 種類 |
|---|---|---|
| 引数の Integer に対して toString() を呼ぶ | i -> i.toString() |
3(インスタンス) |
| 引数を static の toString(int) に渡す | i -> Integer.toString(i) |
1(static) |
前の章で見たとおり、1番と3番は書き方が同じなので、両方が当てはまるとコンパイラは選べません。
パターン2:渡す先のメソッドがオーバーロードされている(String::valueOf)
import java.util.concurrent.*;
import java.util.function.*;
import java.util.*;
import java.util.stream.*;
public class A2 {
static void run(Function<Integer, String> f) { System.out.println("Function: " + f.apply(1)); }
static void run(IntFunction<String> f) { System.out.println("IntFunction: " + f.apply(1)); }
public static void main(String[] args) {
run(String::valueOf);
}
}
A2.java:10: エラー: runの参照はあいまいです
run(String::valueOf);
^
A2のメソッド run(Function<Integer,String>)とA2のメソッド run(IntFunction<String>)の両方が一致します
エラー1個
こちらは、エラーになっているのが valueOf ではなく run のほうである点が違います。String.valueOf には Object 用・int 用など多くのオーバーロードがあるため、String::valueOf は Function<Integer, String> にも IntFunction<String> にもなれます。その結果、どちらの run を呼ぶのかが決まりません。
直し方
import java.util.List;
import java.util.function.*;
import java.util.stream.*;
public class AmbFix {
static void run(Function<Integer, String> f) { System.out.println("Function 版: " + f.apply(1)); }
static void run(IntFunction<String> f) { System.out.println("IntFunction 版: " + f.apply(1)); }
public static void main(String[] args) {
// Integer::toString の代わりに書ける形
Function<Integer, String> a = i -> i.toString(); // ラムダで書く
Function<Integer, String> b = String::valueOf; // 別のメソッドを使う
Function<Integer, String> c = Object::toString; // 親クラスのメソッドを指す
System.out.println(a.apply(10) + " " + b.apply(20) + " " + c.apply(30));
System.out.println(Stream.of(1, 2, 3).map(String::valueOf).collect(Collectors.joining("-")));
// 受け取る側がオーバーロードされているときは、どちらの型かを示す
run((Function<Integer, String>) String::valueOf); // キャストで示す
run((IntFunction<String>) String::valueOf);
run((Integer i) -> String.valueOf(i)); // 引数の型を書いたラムダで示す
run((int i) -> String.valueOf(i));
// String::valueOf は、受け取る型に合わせて呼ばれるメソッドが変わる
Function<Object, String> o = String::valueOf; // valueOf(Object)
Function<char[], String> ch = String::valueOf; // valueOf(char[])
IntFunction<String> in = String::valueOf; // valueOf(int)
DoubleFunction<String> d = String::valueOf; // valueOf(double)
System.out.println(o.apply(List.of(1)) + " " + ch.apply(new char[] {'a', 'b'}) + " " + in.apply(65) + " " + d.apply(65));
}
}
実行結果:
10 20 30
1-2-3
Function 版: 1
IntFunction 版: 1
Function 版: 1
IntFunction 版: 1
[1] ab 65 65.0
| パターン | 直し方 | 書き方 |
|---|---|---|
| 1 | ラムダで書いて、どちらの意味かを示す | i -> i.toString() |
| 1 | あいまいにならない別のメソッドを使う | String::valueOf |
| 1 | 親クラスのメソッドとして指す | Object::toString |
| 2 | キャストで受け取る型を示す | (Function<Integer, String>) String::valueOf |
| 2 | 引数の型を書いたラムダにする | (Integer i) -> String.valueOf(i) |
出力の最後の行は、同じ String::valueOf が、受け取る型によって valueOf(Object)・valueOf(char[])・valueOf(int)・valueOf(double) に解決されることを示しています。char[] を渡した場合は中身の文字列 ab になり、double では 65.0 になっています。メソッド参照は、代入先の型が決まって初めて「どのメソッドか」が決まります。
そのほかのコンパイルエラーと原因
引数の数や戻り値の型が合わないときのメッセージも確認しておきます。
import java.util.function.*;
public class A4 {
public static void main(String[] args) {
Supplier<Integer> s = String::length;
Function<String, Integer> f = String::size;
Function<String, String> g = String::length;
BiFunction<String, String, Integer> h = String::length;
}
}
A4.java:5: エラー: 不適合な型: メソッド参照が無効です
Supplier<Integer> s = String::length;
^
非バインド検索で予期しないインスタンスメソッド length()が見つかりました
A4.java:6: エラー: メソッド参照が無効です
Function<String, Integer> f = String::size;
^
シンボルを見つけられません
シンボル: メソッド size(String)
場所: クラス String
A4.java:7: エラー: 不適合な型: メソッド参照の戻り型が不正です
Function<String, String> g = String::length;
^
intをStringに変換できません:
A4.java:8: エラー: 不適合な型: メソッド参照が無効です
BiFunction<String, String, Integer> h = String::length;
^
クラス Stringのメソッド lengthは指定された型に適用できません
期待値: 引数がありません
検出値: String,String
理由: 実引数リストと仮引数リストの長さが異なります
エラー4個
| 行 | メッセージの要点 | 原因 | 直し方 |
|---|---|---|---|
| 5 | 非バインド検索で予期しないインスタンスメソッド length()が見つかりました | Supplier は引数0個。レシーバーになる引数が無い | Function<String, Integer> で受けるか、text::length のように変数を指定する |
| 6 | シンボルを見つけられません | String に size というメソッドは無い | メソッド名を確かめる(String は length) |
| 7 | メソッド参照の戻り型が不正です intをStringに変換できません | 戻り値の型が合わない | 型引数を Integer にする |
| 8 | 実引数リストと仮引数リストの長さが異なります | length() は引数を取らないのに、2つ目の引数を渡す形になっている | Function<String, Integer> にする |
メソッド参照を var や Object で受けようとした場合のメッセージは次のとおりです。
public class A5 {
public static void main(String[] args) {
var v = String::valueOf;
Object o = String::valueOf;
}
}
A5.java:3: エラー: ローカル変数vの型を推論できません
var v = String::valueOf;
^
(メソッド参照には明示的なターゲット型が必要です)
A5.java:4: エラー: 不適合な型: Objectは機能インタフェースではありません
Object o = String::valueOf;
^
エラー2個
メソッド参照は、それだけでは型が決まりません。代入先や引数の型が関数型インターフェースであることが必要です。javac のメッセージでは、関数型インターフェースが「機能インタフェース」と表記されています。
ラムダとメソッド参照のどちらで書くか
| 状況 | おすすめ | 例 |
|---|---|---|
| メソッドを1つ呼ぶだけ | メソッド参照 | map(Item::name) |
| 引数を加工する・定数を足す | ラムダ | map(s -> s.substring(1)) |
| 否定したい | Predicate.not +メソッド参照、またはラムダ |
filter(Predicate.not(String::isBlank)) |
| 「参照はあいまいです」が出る | ラムダ | map(i -> i.toString()) |
| 呼び出し時に対象を評価したい | ラムダ | () -> name.length() |
| クラス名が長く、ラムダのほうが短い | ラムダ | 読みやすいほうを選ぶ |
メソッド参照は「何をするか」がメソッド名で伝わるのが利点です。短くならない場合や、引数の流れが見えたほうがわかりやすい場合は、ラムダのままで問題ありません。
よくある質問
Q. メソッド参照とラムダで、実行結果は変わりますか? A. メソッドを呼ぶだけの処理なら同じ結果になります。違いが出るのは「変数::メソッド」の形で、:: の左側が作成時に1回だけ評価される点です。変数が null なら、呼び出す前の作成時点で NullPointerException になります。
Q. System.out::println は4種類のどれですか?
A. 2番の「特定のオブジェクトのインスタンスメソッド参照」です。System.out は PrintStream のオブジェクトが入った static フィールドで、そのオブジェクトの println を指しています。
Q. 引数が2つあるメソッドも参照にできますか?
A. できます。String::equalsIgnoreCase を BiFunction<String, String, Boolean> で受けると、1つ目の引数がレシーバー、2つ目がメソッドの引数になります。Integer::sum のような static メソッドなら、2つの引数がそのまま渡されます。
Q. 配列を作るメソッド参照は、どこで使いますか?
A. Stream の toArray に渡す使い方が代表的です。toArray(String[]::new) と書くと、String の配列として結果を受け取れます。
まとめ
- メソッド参照は「メソッドを呼ぶだけのラムダ」の短い書き方。 static・特定のオブジェクト・任意のオブジェクト・コンストラクタの4種類に、配列コンストラクタ参照が加わる。
クラス名::インスタンスメソッドでは、関数の第1引数がレシーバーになる。 関数の引数はメソッドの引数より1つ多い。- コンストラクタ参照は、受け取る型の引数に合わせてコンストラクタが選ばれる。
ArrayList::newを Function として渡すとArrayList(int)になる。 変数::メソッドは作成時に左側が評価される。 null なら作成時点で NullPointerException。ラムダは呼び出し時。- 「参照はあいまいです」は、static とインスタンスの両方に当てはまるか、渡す先がオーバーロードされているときに出る。 ラムダで書くか、キャストで型を示す。
- メソッド参照は代入先の型で意味が決まる。 var や Object では受けられない。
参考・出典
- The Java Tutorials「Method References」 https://docs.oracle.com/javase/tutorial/java/javaOO/methodreferences.html
- The Java Language Specification, Java SE 21 Edition「15.13 Method Reference Expressions」 https://docs.oracle.com/javase/specs/jls/se21/html/jls-15.html#jls-15.13
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「Javaメソッド参照(::)の使い方|4種類とラムダ対応表」
株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。