Movatterモバイル変換


[0]ホーム

URL:


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());
Scalaval 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");
Scalaval 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));
Scalaval op = Integer.sum_
println(op.apply(1, 2))
thisメソッド参照class Example1 {
  public void method1() {
   IntSupplier supplier =this::method2;
    System.out.println(supplier.getAsInt());
  }

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

  privatestatic intmethod2() {
    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;}@Overridepublic String toString() {return getClass().getSimpleName() + "." + n;}}
class SubValue extends Value {public SubValue(int n) {super(n);}@Overridepublic 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;}// インスタンスメソッドのisOkpublic booleanisOk() {return n % 3 == 0;}// staticメソッドのisOkpublicstatic booleanisOk(Value value) {return value.n % 2 == 0;}@Overridepublic 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.javaMethodReferenceExample2.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.javaMethodReferenceExample2.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.javaMethodReferenceExample.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 {@FunctionalInterfacepublic 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.PrintStreamat 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]

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

// シリアライズ可能な関数型インターフェース@FunctionalInterfacepublic 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.MyFactoryat 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 {@FunctionalInterfacepublic 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> Tdeserialize(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 typeat 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.InvocationTargetExceptionat 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 moreCaused by:java.lang.IllegalArgumentException: Invalid lambda deserializationat 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 {

  classInner {
  }

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

  voidmethod() {
    classInner {
    }

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

 static voidmethod() {
    classInner {
    }

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

  classInner {
  }

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

  voidmethod() {
    classInner {
    }

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

 static voidmethod() {
    classInner {
    }

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

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

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 


[8]ページ先頭

©2009-2025 Movatter.jp