S-JIS[2014-03-23/2020-06-10] 変更履歴

Javaメソッド参照・コンストラクター参照

Javaのメソッド参照およびコンストラクター参照について。


メソッド参照

メソッド参照は、JDK1.8で導入された構文。
関数型インターフェース(抽象メソッドが1つだけ定義されているインターフェース)の変数にメソッドそのものを代入することが出来る。
これを「メソッド参照」と呼ぶ。

メソッド参照は以下のようにして指定する。

// staticメソッドの場合(場合によってはインスタンスメソッドも)
クラス名::メソッド名
// インスタンスメソッドの場合
インスタンス変数名::メソッド名

要するに、呼び出したいメソッド名の直前を「::」にし、メソッドの引数部分(丸括弧部分)を除去すれば、メソッド参照として渡すことが出来る。
(C++ではnamespace(名前空間)(Javaのパッケージ相当)の区切り文字が「::」だったので、それを踏襲したのだろう)

メソッド参照で代入できる関数型インターフェースは、抽象メソッドの引数の個数・型と、代入したいメソッドの引数の個数・型が一致している必要がある。
代入したいメソッドがオーバーロードされている場合(同名で引数違いのメソッドがある場合)は、代入先の関数型インターフェースのメソッドのシグネチャーが一致しているものが自動的に選ばれる。

パターン 代入する
メソッド
ラムダ式   メソッド参照 備考
代入先の引数が0個の場合 staticメソッド () -> クラス名.メソッド名() ←同じ→ クラス名::メソッド名  
代入先の引数が1個の場合 a -> クラス名.メソッド名(a)  
代入先の引数が2個の場合 (a0, a1) -> クラス名.メソッド名(a0, a1)  
代入先の引数が3個の場合 (a0, a1, a2) -> クラス名.メソッド名(a0, a1, a2)  
代入先の引数が1個で、変数の型とクラスが一致している(または親クラスである)場合[/2014-06-21] インスタンスメソッド (変数名) -> 変数名.メソッド名() ←同じ→
代入先の引数が2個で、変数の型とクラスが一致している場合[2014-04-12] (変数名, a) -> 変数名.メソッド名(a)
代入先の引数が0個の場合 インスタンスメソッド () -> 変数名.メソッド名() ←同じ→ 変数名::メソッド名  
代入先の引数が1個の場合 a -> 変数名.メソッド名(a)  
代入先の引数が2個の場合 (a0, a1) -> 変数名.メソッド名(a0, a1)  
代入先の引数が3個の場合 (a0, a1, a2) -> 変数名.メソッド名(a0, a1, a2)  

メソッド参照の例

 

備考
引数0個 メソッド参照 String s = "abc";
IntSupplier supplier = s::length;
System.out.println(supplier.getAsInt());
IntSupplierは、引数なしで戻り型がintのメソッド(getAsInt)を持つ。

s.length()」のlengthメソッドを渡したいので、
「length」の直前を「::」にする。

ちなみに、「"abc"::length」といった書き方も出来る。
ラムダ式 String s = "abc";
IntSupplier supplier = () -> s.length();
System.out.println(supplier.getAsInt());
匿名クラス String s = "abc";
IntSupplier supplier = new IntSupplier() {
  @Override
  public int getAsInt() {
    return s.length();
  }
};
System.out.println(supplier.getAsInt());
Scala val s = "abc"
val supplier = s.length _
println(supplier.apply())
引数1個 メソッド参照 Consumer<String> c = System.out::println;
c.accept("abc");
Consumerは、引数が1個で戻り型がvoidのメソッド(accept)を持つ。

System.out.println(String)」のprintlnメソッドを渡したいので、
「println」の直前を「::」にする。
(「System.out」はSystemクラスのstaticフィールド「out」である)
ラムダ式 Consumer<String> c = (String s) -> System.out.println(s);
c.accept("abc");
匿名クラス Consumer<String> c = new Consumer<String>() {
  @Override
  public void accept(String s) {
    System.out.println(s);
  }
};
c.accept("abc");
Scala val c : (String) => Unit = System.out.println
c.apply("abc")
引数2個 メソッド参照 IntBinaryOperator op = Integer::sum;
System.out.println(op.applyAsInt(1, 2));
IntBinaryOperatorは、引数が2個でintを返すメソッド(applyAsInt)を持つ。

Integer.sum(int, int)」のsumメソッドを渡したいので、
「sum」の直前を「::」にする。
(「sum」はIntegerクラスのstaticメソッド)
ラムダ式 IntBinaryOperator op = (int n1, int n2) -> Integer.sum(n1, n2);
System.out.println(op.applyAsInt(1, 2));
匿名クラス IntBinaryOperator op = new IntBinaryOperator() {
  @Override
  public int applyAsInt(int left, int right) {
    return Integer.sum(left, right);
  }
};
System.out.println(op.applyAsInt(1, 2));
Scala val op = Integer.sum _
println(op.apply(1, 2))
this メソッド参照 class Example1 {
  public void method1() {
    IntSupplier supplier = this::method2;
    System.out.println(supplier.getAsInt());
  }

  public int method2() {
    return 123;
  }
}
自分自身のクラスにあるインスタンスメソッドを渡す場合は、「this::」を付ける。[2014-04-01]
(thisは省略できない)
static メソッド参照 class Example2 {
  public void method1() {
    IntSupplier supplier = Example2::method2;
    System.out.println(supplier.getAsInt());
  }

  private static int method2() {
    return 1234;
  }
}
自分自身のクラスにあるstaticメソッドを渡す場合は、「自クラス名::」を付ける。[2014-04-01]
(クラス名は省略できない)
(staticメソッドに対して「this::」を付けると、
「staticのバインディングされたメソッド参照」というコンパイルエラーになる)
インスタンスメソッド
クラス名渡し
引数1個
メソッド参照 ToIntFunction<String> f = String::length;
int n = f.applyAsInt("abc");
System.out.println(n);
インスタンスメソッドを渡す際に「クラス名::」を使っている例。[2014-04-12]
引数が1つ(A a)の関数を渡す場合、
a.method()」という呼び出しになるなら、
A::method」という形でクラス名を用いたメソッド参照が渡せる。
ラムダ式 ToIntFunction<String> f = s -> s.length();
int n = f.applyAsInt("abc");
System.out.println(n);
インスタンスメソッド
クラス名渡し
引数2個
メソッド参照 BiFunction<List<String>, String, Boolean> f = List<String>::add;

List<String> list = new ArrayList<>();
f.apply(list, "abc");
System.out.println(list);
インスタンスメソッドを渡す際に「クラス名::」を使っている例。[2014-04-12]
引数が2つ(A a, B b)ある関数を渡す場合、
a.method(b)」という呼び出しになるなら、
A::method」という形でクラス名を用いたメソッド参照が渡せる。
ラムダ式 BiFunction<List<String>, String, Boolean> f = (list, s) -> list.add(s);

List<String> list = new ArrayList<>();
f.apply(list, "abc");
System.out.println(list);
ジェネリクス メソッド参照 class Example {

  static <T> void methodG(T arg) { }

  static void test() {
    Consumer<String> c = Example::<String> methodG;
  }
}
型引数を明示的に指定したい場合は、「::」の直後に「<型引数>」を指定する。[2014-04-18]
(左記の例だと、明示しなくても推論してくれるが)

オーバーライドされたメソッドのメソッド参照

メソッド参照はラムダ式と同義なので、「クラス名::メソッド名」の形式で指定しても、対象となるインスタンスのクラスのメソッドが呼ばれる。[2014-06-21]
つまり、対象インスタンスが“メソッド参照で指定されたクラス”のサブクラスで、“メソッド参照で指定されたメソッド”がオーバーライドされている場合、サブクラスのメソッドが呼ばれる。
(つまり、「クラス名::メソッド名」形式での指定は、呼び出すメソッド(のクラス)を固定しているわけではない)

class Value {
	protected final int n;

	public Value(int n) {
		this.n = n;
	}

	public boolean isOk() {
		return n % 3 == 0;
	}

	@Override
	public String toString() {
		return getClass().getSimpleName() + "." + n;
	}
}
class SubValue extends Value {

	public SubValue(int n) {
		super(n);
	}

	@Override
	public boolean isOk() {
		return n % 5 == 0;
	}
}
	public static void main(String... args) {
		List<Value> list = Arrays.asList(new Value(1), new Value(2), new Value(3), new SubValue(3), new SubValue(4), new SubValue(5));

		// ラムダ式で指定
		System.out.println(list.stream().filter(value -> value.isOk()).collect(Collectors.toList()));

		// メソッド参照で指定
		System.out.println(list.stream().filter(Value::isOk).collect(Collectors.toList()));
	}

↓出力結果

[Value.3, SubValue.5]
[Value.3, SubValue.5]

ラムダ式を使ってもメソッド参照を使っても同じ結果が出力されている。

つまり、メソッド参照でValueクラスを指定していても、インスタンスがSubValueの場合はSubValueのisOk()が呼び出されている。


あるクラスのStreamであっても、メソッド参照で親クラスを指定することが出来る。
この場合もサブクラスのメソッドが呼ばれる。

	static void execute2() {
		List<SubValue> list = IntStream.rangeClosed(1, 10).mapToObj(SubValue::new).collect(Collectors.toList());

		// ラムダ式で指定(valueの型はSubValueになってる)
		System.out.println(list.stream().filter(value -> value.isOk()).collect(Collectors.toList()));

		// ラムダ式で親クラスを指定
		System.out.println(list.stream().filter((Value value) -> value.isOk()).collect(Collectors.toList()));

		// メソッド参照(Streamの型引数と同クラス)で指定
		System.out.println(list.stream().filter(SubValue::isOk).collect(Collectors.toList()));

		// メソッド参照で親クラスを指定
		System.out.println(list.stream().filter(Value::isOk).collect(Collectors.toList()));
	}

↓出力結果

[SubValue.5, SubValue.10]
[SubValue.5, SubValue.10]
[SubValue.5, SubValue.10]
[SubValue.5, SubValue.10]

曖昧なメソッド参照

クラス名::メソッド名」形式のメソッド参照は、構文上はstaticメソッドでもインスタンスメソッドでも指定できるが、使用時に一意に決定できない場合(曖昧な場合)はコンパイルエラーになる。[2014-06-21]

class Value {
	protected final int n;

	public Value(int n) {
		this.n = n;
	}

	// インスタンスメソッドのisOk
	public boolean isOk() {
		return n % 3 == 0;
	}

	// staticメソッドのisOk
	public static boolean isOk(Value value) {
		return value.n % 2 == 0;
	}

	@Override
	public String toString() {
		return getClass().getSimpleName() + "." + n;
	}
}

このValueクラスでは、isOkというインスタンスメソッドとstaticメソッドを定義している。(引数が違うので定義できる)

	public static void main(String... args) {
		List<Value> list = IntStream.rangeClosed(1, 10).mapToObj(Value::new).collect(Collectors.toList());

		// ラムダ式によるインスタンスメソッド呼び出し
		System.out.println(list.stream().filter(value -> value.isOk()).collect(Collectors.toList()));

		// ラムダ式によるstaticメソッド呼び出し
		System.out.println(list.stream().filter(value -> Value.isOk(value)).collect(Collectors.toList()));

		// メソッド参照→コンパイルエラー
//×		System.out.println(list.stream().filter(Value::isOk).collect(Collectors.toList()));
	}

↓実行結果

[Value.3, Value.6, Value.9]
[Value.2, Value.4, Value.6, Value.8, Value.10]

ラムダ式によるメソッド呼び出しは、どちらのメソッドを呼び出すのか明示するので、問題ない。

メソッド参照を指定しようとすると、適用可能なisOkメソッドが複数存在するので以下のようなコンパイルエラーになる。

Eclipseの場合
Ambiguous method reference: both isOk() and isOk(Value) from the type Value are eligible
javacの場合
D:\tmp> javac MethodReferenceExample2.java
MethodReferenceExample2.java:39: エラー: メソッド参照が無効です
                System.out.println(list.stream().filter(Value::isOk).collect(Collectors.toList()));
                                                        ^
  staticでないメソッド isOk()をstaticコンテキストから参照することはできません
注意:一部のメッセージは簡略化されています。-Xdiags:verboseで再コンパイルして完全な出力を取得してください
エラー1個
javac -Xdiags:verboseの場合
D:\tmp> javac -Xdiags:verbose MethodReferenceExample2.java
MethodReferenceExample2.java:39: エラー: メソッド参照が無効です
                System.out.println(list.stream().filter(Value::isOk).collect(Collectors.toList()));
                                                        ^
  staticでないメソッド isOk()をstaticコンテキストから参照することはできません
MethodReferenceExample2.java:39: エラー: インタフェース Stream<T>のメソッド filterは指定された型に適用できません。
                System.out.println(list.stream().filter(Value::isOk).collect(Collectors.toList()));
                                                       ^
  期待値: Predicate<? super Value>
  検出値: Value::isOk
  理由: 引数の不一致: メソッド参照が無効です
      isOkの参照はあいまいです
        Valueのメソッド isOk(Value)とValueのメソッド isOk()の両方が一致します
  Tが型変数の場合:
    インタフェース Streamで宣言されているTはObjectを拡張します
エラー2個

javac(Windows版1.8.0-b132)の場合、「staticでないメソッドをstaticコンテキストから参照できない」というエラーメッセージが出ているが、これはミスリード。
javacに-Xdiags:verbose」オプションを付けると、「参照は曖昧である」というエラーメッセージが出てくる。 これが本件のエラー。

どちらかのisOkメソッドをコメントアウトして1つだけにすれば、staticメソッドでもインスタンスメソッドでもコンパイルは通り、実行できる。


ジェネリクスによって曖昧になるメソッド参照

同名で引数が異なるメソッド(オーバーロードされているメソッド)をメソッド参照として使う場合でも、使用箇所の型に合致するメソッドがひとつだけなら、メソッド参照として使用できる。[2020-06-10]
合致するメソッドが複数ある(一意に解決できない)場合は「メソッド参照が無効」というエラーになる。

が、ジェネリクスを使っていると、一意に定まりそうなケースでもコンパイルエラーになる。 (Java8〜Java14)

 
// メソッドが1つ
class P1 {
  public static int parse(String s) {
    return Integer.parseInt(s);
  }
}
// 同名メソッドがある
class P2 {
  public static int parse(String s) {
    return Integer.parseInt(s);
  }

  public static int parse(String s, boolean b) {
    return Integer.parseInt(s);
  }
}
// Functionの型を特定
class F1 {
  public static F1 of(Function<String, Integer> f) {
    return 〜;
  }
}
F1.of(P1::parse);

コンパイルOK
F1.of(P2::parse);

コンパイルOK
// Functionの型引数がジェネリクス
class F2 {
  public static <A, B> F2 of(Function<A, B> f) {
    return 〜;
  }
}
F2.of(P1::parse);

コンパイルOK
F2.of(P2::parse);

エラー: 不適合な型: 型変数A,Bを推論できません
F2.of(P2::parse);
     ^
(引数の不一致: メソッド参照が無効です
  不適合な型: ObjectをStringに変換できません:)

-Xdiags:verbose」オプションを付けてコンパイルすると、もう少し詳細なエラーメッセージが出る。

> javac -Xdiags:verbose MethodReferenceExample.java
MethodReferenceExample.java:37: エラー: 不適合な型: 型変数A,Bを推論できません
                F2.of(P2::parse);
                     ^
    (引数の不一致: メソッド参照が無効です
      parseに適切なメソッドが見つかりません(Object)
          メソッド P2.parse(String)は使用できません
            (引数の不一致: ObjectをStringに変換できません:)
          メソッド P2.parse(String,boolean)は使用できません
            (実引数リストと仮引数リストの長さが異なります))
  A,Bが型変数の場合:
    メソッド <A,B>of(Function<A,B>)で宣言されているA extends Object
    メソッド <A,B>of(Function<A,B>)で宣言されているB extends Object
エラー1個

「引数の不一致: ObjectをStringに変換できません」と出ているが、メソッドを1つしか定義していないP1ではちゃんとコンパイルが通っているので、なんか納得できない…。 引数の個数違いなんて、Functionに合致しないのは明白だし。
ただ、問題はメソッド参照の曖昧さの解決ではなく、ジェネリクスの解釈の方だと思われる。

解決策
ジェネリクスの型を明示する方法 F2.<String, Integer> of(P2::parse);
メソッド参照をキャストする方法 F2.of((Function<String, Integer>) P2::parse);

参考: RayStark77さんのツイート


インスタンスのキャプチャー

インスタンス(変数)に対するメソッド参照を書く場合は、ちょっと注意を要する。[2017-07-26]

以下のメソッド参照とラムダ式は、同等に見えるだろうか?

import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Supplier;
	// メソッド参照
	Supplier<Integer> s = new AtomicInteger()::incrementAndGet;
	// ラムダ式
	Supplier<Integer> s = () -> new AtomicInteger().incrementAndGet();
実行例
  メソッド参照 ラムダ式
ソース
Supplier<Integer> s = new AtomicInteger()::incrementAndGet;
System.out.println(s.get());
System.out.println(s.get());
System.out.println(s.get());
Supplier<Integer> s = () -> new AtomicInteger().incrementAndGet();
System.out.println(s.get());
System.out.println(s.get());
System.out.println(s.get());
実行結果 1
2
3
1
1
1

このラムダ式は呼ばれる度にAtomicIntegerがインスタンス化される(初期値は0)ので、常に1が表示されている。
一方、このメソッド参照はAtomicIntegerインスタンスが共有されているので、呼ばれる度に同じインスタンスに対してインクリメントされる。

このメソッド参照は、実体としては以下のようになる。

	AtomicInteger n = new AtomicInteger();
	Supplier<Integer> s = n::incrementAndGet;

つまり、「::」の左側は最初に評価され、メソッド参照の外側で定義される。そしてそのインスタンスがメソッド参照内で使われる(キャプチャーされる)。

参考: ashigeruさんのサンプル


Eclipseのキャプチャーのバグ

旧バージョンのEclipseでは、メソッド参照のキャプチャーに関してバグがある。[2017-07-26]

package com.example;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
public class MethodReferenceCaptureExample {

	@FunctionalInterface
	public interface MyConsumer<T> extends Serializable {
		void accept(T t);
	}
	public static void main(String[] args) throws Exception {
		MyConsumer<String> f = System.out::println;

		byte[] buf = serialize(f);
		MyConsumer<String> f2 = deserialize(buf);
		f2.accept("abc");
	}
〜
}

このメソッド参照で使っているSystem.outはキャプチャーされる。
その状態でこの関数をシリアライズすると、キャプチャーされたSystem.out(PrintStream)はシリアライズ可能でないので、例外が発生するのが正しい。
(Oracleのjavacでコンパイルして実行すれば、仕様通りに例外が発生する)

Exception in thread "main" java.io.NotSerializableException: java.io.PrintStream
	at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184)
	at java.io.ObjectOutputStream.writeArray(ObjectOutputStream.java:1378)
	at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1174)
	at java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1548)
	at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1509)
	at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1432)
	at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1178)
	at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)
	at com.example.MethodReferenceCaptureExample.serialize(MethodReferenceCaptureExample.java:30)
	at com.example.MethodReferenceCaptureExample.main(MethodReferenceCaptureExample.java:20)

ところが、一部のバージョンのEclipseではシリアライズできてしまう。

Eclipseのバージョン 実行結果
Eclipse 4.4.2(Luna) 実行できる(バグ)
Eclipse 4.5.2(Mars) 実行できる(バグ)
Eclipse 4.6.3(Neon) 例外発生(仕様通り)
Eclipse 4.7.0(Oxygen) 例外発生(仕様通り)

PrintStream ps = System.out; MyConsumer<String> f = ps::println;」とすれば、どのバージョンでも仕様通りに例外が発生する。
また、ラムダ式で書けば、仕様通りに例外が発生する。

参考: stackoverflowのWhat is the difference between a lambda and a method reference at a runtime level?


シリアライズ

関数型インターフェースがSerializableを継承していると、その関数型インターフェース(のオブジェクト)はシリアライズすることが出来る。[2017-07-26]

実際にシリアライズする場合は、その関数型インターフェースに代入したメソッド参照はシリアライズ可能である必要がある。
(メソッド参照でキャプチャーしているインスタンスがあれば、そのインスタンスはシリアライズ可能である必要がある)

// シリアライズ可能な関数型インターフェース
@FunctionalInterface
public interface MySupplier<T> extends Serializable {
	public T get();
}
class MyFactory /* implements Serializable */ {

	public String create() {
		return "zzz";
	}
}
		MyFactory factory = new MyFactory();
		MySupplier<String> f = factory::create;	// factoryをキャプチャーしている
		byte[] buf = serialize(f);

メソッド参照で使用しているインスタンス(上記のMyFactory)がSerializableを実装していないと、関数(上記のMySupplier)のシリアライズ時に例外が発生する。

Exception in thread "main" java.io.NotSerializableException: com.example.MyFactory
	at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184)
	at java.io.ObjectOutputStream.writeArray(ObjectOutputStream.java:1378)
	at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1174)
	at java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1548)
	at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1509)
	at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1432)
	at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1178)
	at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)
	at com.example.MethodReferenceSerializeExample.serialize(MethodReferenceSerializeExample.java:110)
	at com.example.MethodReferenceSerializeExample.main(MethodReferenceSerializeExample.java:16)

ラムダ式のシリアライズ


Eclipseのシリアライズのバグ

Eclipseの一部のバージョンでは、メソッド参照のシリアライズに関してバグがある。[2017-07-26]

package com.example;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
public class MethodReferenceSerializeExample {

	@FunctionalInterface
	public interface MyFunction<T, R> extends Serializable {
		public R apply(T t);
	}
	static class Wrapper<T> {
		private T s;

		public Wrapper(T s) {
			this.s = s;
		}

		public T get() {
			return s;
		}
	}
	public static void main(String[] args) throws Exception {
		MyFunction<Wrapper<String>, String> f;
		// f = w -> w.get();
		f = Wrapper::get;

		byte[] buf = serialize(f);
		MyFunction<Wrapper<String>, String> f2 = deserialize(buf);
		System.out.println(f2.apply(new Wrapper<>("www")));
	}
	// シリアライズ
	static byte[] serialize(Object obj) throws IOException {
		try (ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
			try (ObjectOutputStream oos = new ObjectOutputStream(bos)) {
				oos.writeObject(obj);
			}
			return bos.toByteArray();
		}
	}

	// デシリアライズ
	static <T> T deserialize(byte[] bytes) throws IOException, ClassNotFoundException {
		try (ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
		     ObjectInputStream ois = new ObjectInputStream(bis)) {
			@SuppressWarnings("unchecked")
			T r = (T) ois.readObject();
			return r;
		}
	}
}

上記のソースは、正常に実行されれば「www」が表示されるが、バグが発現すると以下のような例外が発生する。
(Oracleのjavacでコンパイルすれば、正常に動作する)

Exception in thread "main" java.io.IOException: unexpected exception type
	at java.io.ObjectStreamClass.throwMiscException(ObjectStreamClass.java:1582)
	at java.io.ObjectStreamClass.invokeReadResolve(ObjectStreamClass.java:1154)
	at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1817)
	at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1353)
	at java.io.ObjectInputStream.readObject(ObjectInputStream.java:373)
	at com.example.MethodReferenceSerializeExample.deserialize(MethodReferenceSerializeExample.java:54)
	at com.example.MethodReferenceSerializeExample.main(MethodReferenceSerializeExample.java:35)
Caused by: java.lang.reflect.InvocationTargetException
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at java.lang.invoke.SerializedLambda.readResolve(SerializedLambda.java:230)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at java.io.ObjectStreamClass.invokeReadResolve(ObjectStreamClass.java:1148)
	... 5 more
Caused by: java.lang.IllegalArgumentException: Invalid lambda deserialization
	at com.example.MethodReferenceSerializeExample.$deserializeLambda$(MethodReferenceSerializeExample.java:1)
	... 15 more
Eclipseのバージョン 実行結果
Eclipse 4.4.2(Luna) 正常
Eclipse 4.5.2(Mars) 正常
Eclipse 4.6.3(Neon) 例外発生(バグ)
Eclipse 4.7.0(Oxygen) 例外発生(バグ)

参考: eclipse Bugzilla - Bug 449467


コンストラクター参照

メソッド参照と同様に、コンストラクターも関数型インターフェース(抽象メソッドが1つだけ定義されているインターフェース)の変数に代入することが出来る。
これを「コンストラクター参照」と呼ぶ。

コンストラクター参照は以下のようにして指定する。

クラス名::new

代入したいコンストラクターに複数種類ある場合(引数違いのコンストラクターがある場合)は、代入先の関数型インターフェースの引数の型が一致しているものが自動的に選ばれる。

コンストラクター参照   ラムダ式 備考
クラス名::new ←同じ→ () -> new クラス名() 代入先の引数が0個の場合
a -> new クラス名(a) 代入先の引数が1個の場合
(a0, a1) -> new クラス名(a0, a1) 代入先の引数が2個の場合

コンストラクター参照の例

 

備考
引数
0個
コンストラクター参照 Supplier<List<String>> supplier = ArrayList::new;
List<String> list = supplier.get();
 
ラムダ式 Supplier<List<String>> supplier = () -> new ArrayList<>();
List<String> list = supplier.get();
匿名クラス Supplier<List<String>> supplier = new Supplier<List<String>>() {
  @Override
  public List<String> get() {
    return new ArrayList<>();
  }
};
List<String> list = supplier.get();
引数
1個
コンストラクター参照 IntFunction<List<String>> factory = ArrayList::new;
List<String> array = factory.apply(10);
 
ラムダ式 IntFunction<List<String>> factory = (int n) -> new ArrayList<>(n);
List<String> array = factory.apply(10);
匿名クラス IntFunction<List<String>> factory = new IntFunction<List<String>>() {
  @Override
  public List<String> apply(int value) {
    return new ArrayList<>(value);
  }
};
List<String> array = factory.apply(10);
配列 コンストラクター参照 IntFunction<String[]> factory = String[]::new;
String[] array = factory.apply(10);
配列もコンストラクター参照にすることが出来る。
ラムダ式 IntFunction<String[]> factory = (int n) -> new String[n];
String[] array = factory.apply(10);
匿名クラス IntFunction<String[]> factory = new IntFunction<String[]>() {
  @Override
  public String[] apply(int value) {
    return new String[value];
  }
};
String[] array = factory.apply(10);

ローカルクラスのコンストラクター参照の問題

JDK1.8.0_45では、ローカルクラスのコンストラクター参照にバグがあるっぽい。[2015-06-17]

  メンバークラス staticでないメソッド内の
ローカルクラス
staticメソッド内の
ローカルクラス
コード例 public class Outer {

  class Inner {
  }

  void method() {
    Supplier<Inner> s = Inner::new;
  }
}
public class Outer {

  void method() {
    class Inner {
    }

    Supplier<Inner> s = Inner::new;
  }
}
public class Outer {

  static void method() {
    class Inner {
    }

    Supplier<Inner> s = Inner::new;
  }
}
javacによる
コンパイル
OK Outer.java:9: エラー: 不適合な型: コンストラクタ参照が無効です
    Supplier<Inner> s = Inner::new;
                        ^
  コンストラクタInner()にアクセスできません
  内部クラスを囲む型Outerのインスタンスがスコープ内にありません
エラー1個
OK
Eclipse4.4による
コンパイル
OK OK OK
ラムダ式による
代替例
public class Outer {

  class Inner {
  }

  void method() {
    Supplier<Inner> s = () -> new Inner();
  }
}
public class Outer {

  void method() {
    class Inner {
    }

    Supplier<Inner> s = () -> new Inner();
  }
}
public class Outer {

  static void method() {
    class Inner {
    }

    Supplier<Inner> s = () -> new Inner();
  }
}

Java目次へ戻る / 新機能へ戻る / 技術メモへ戻る
メールの送信先:ひしだま