2016年10月22日土曜日

[JavaSE8 Goldへの道] その4 メソッド参照



JavaSE8 Goldへの道(Upgrade to Java SE 8 Programmer 1Z0-810 試験対策)4回目です。
いよいよStreamAPI・・・に入る前に、メソッド参照を説明しておきます(;´∀`)





メソッド参照とは

Java8で新しく導入された、ラムダ式を簡略化する記述法です。
List list = Arrays.asList("あ", "い", "う");

//ラムダ式
list.forEach(e -> System.out.println(e));

//メソッド参照
list.forEach(System.out::println);
実行結果:






forEachメソッドはArrayListが実装しているIterableインタフェースに用意されたデフォルトメソッドです。引数にConsumer型を取り、リストの各要素についてConsumer#acceptまたはラムダ式を呼び出します。

ラムダ式で呼び出すメソッドが1つだけの場合、より簡略化したメソッド参照という記述ができます。
メソッド参照の構文は以下の通りです。

クラス名/インスタンス変数名::メソッド名

引数も無くなって非常に簡潔になりました。
「::」というのはJavaでは無かった記法ですが、C++の記述法からきてるのかもしれませんね。

注意としては、参照するメソッドの前だけが「::」になります。
「System.out」の部分は通常のクラス変数の参照なのでこれまで通り「.」です。

当然ながら、ブロックで複数の文がある場合はメソッド参照にできません。


メソッド参照の種類

メソッド参照は以下の3種類があります。
  1. staticメソッド参照
  2. インスタンスメソッド参照
  3. コンストラクタ参照

staticメソッド参照

呼び出すメソッドがstaticメソッドの場合、「クラス名::メソッド名」で記述します。
//ラムダ式
Function<String, Integer> lambda = str -> Integer.parseInt(str);
System.out.println(lambda.apply("100"));

//メソッド参照
Function<String, Integer> ref = Integer::parseInt;
System.out.println(ref.apply("200"));

メソッド参照はラムダ式なので、引数と戻り値の方が一致する関数型インタフェースの変数に入れられます。
//ラムダ式
Comparator<Integer> lambda = (x, y) -> Integer.compare(x, y);
System.out.println(lambda.compare(10, 20));

//メソッド参照
Comparator<Integer> ref = Integer::compare;
System.out.println(ref.compare(30, 40));

引数が2つの場合です。引数が複数あっても型があってさえいれば省略可能です。


インスタンスメソッド参照

インスタンスメソッドの場合、「インスタンス変数::メソッド名(またはクラス名::メソッド名)」で記述します。後者については後述します。
String s = "テスト";
//ラムダ式
IntSupplier lambda = () -> s.length();
System.out.println(lambda.getAsInt());

//メソッド参照
IntSupplier ref = s::length;
System.out.println(ref.getAsInt());

IntSupplierは引数なしで戻り型がintのメソッド(getAsInt)を持つ関数型インタフェースです。
一番最初にあげた「System.out::println」もインスタンスメソッド参照です。

もう一つの、インスタンスメソッド参照なのにクラス名を使うケースです。

//ラムダ式
UnaryOperator<String> ref = new UnaryOperator<String>(){
 @Override
 public String apply(String t) {
  t.toUpperCase();
 };
};
真ん中のコメントアウトしている行は、変数sが未定義なのでコンパイルエラーとなります。最後のようにクラス名::インスタンスメソッド名と記述すると動きます。

どのインスタンスに対してインスタンスメソッドを呼び出すかを変数名で指定できない場合は、クラス名で指定します。
非常にややこしいですが(;´∀`)

ポイントは2つあって、まずラムダ式に書いているsはいいのか?というと、これは匿名クラスで以下のように書くのと同じで、メソッド定義の変数と同じと考えればよいでしょう。
UnaryOperator<String> ref = new UnaryOperator<String>(){
 @Override
 public String apply(String t) {
  t.toUpperCase();
 };
};

2点目、じゃあ変数を宣言していれば良いかというとそうではありません。UnaryOperatorはapply(T)と引数を取るメソッドのインタフェースなので、toUpperCaseは合わないためコンパイルエラーとなります。

この場合Supplier<String>なら動きます。
String s = "ややこしい";

//UnaryOperator#apply(T)と型が合わない
//UnaryOperator<String> uo = s::toUpperCase;

//これなら通る
Supplier<String> sup = s::toUpperCase;
System.out.println(sup.get());

//逆にこういうこと
BiPredicate<String, String> bip = String::equals;
System.out.println(bip.test("あ", "い"));
最後の例はクラス名で指定する別のケースです。
このように、第1引数がレシーバとなるインスタンス、第2引数以降がメソッドに渡す引数となります。



コンストラクタ参照

通常インスタンス生成はnew ~と書きますが、これもメソッド参照で記述できます。
//コンストラクタ参照の例
Supplier<List<String>> sup = ArrayList<String>::new;
//型推論が効けば省略可
Supplier<List<String>> sup = ArrayList::new;
//普通はこう
List<String> l = new ArrayList<>();
//ラムダ式だとこう
Supplier<List<String>> sup2 = () -> new ArrayList<>();
コンストラクタ参照に限らず、型指定があるメソッド・コンストラクタで型推論が効く場合は省略可能です。この時ダイアモンド演算子ではなく<>自体を書きません。上記例ではArrayList<>::newと書くとコンパイルエラーとなります。

また、配列の生成もコンストラクタ参照が可能です。

//ラムダ式
Function<Integer, String[]> lambda = length -> new String[length];

//コンストラクタ参照
Function<Integer, String[]> ref = String[]::new;



曖昧なメソッド参照

同じ名前のstaticメソッドとインスタンスメソッドがあり、メソッド参照ではどちらか特定しきれない場合はコンパイルエラーとなります。
//コンパイルエラー
Function<Double, Integer> func = Double::hashCode;

//どちらかじゃないとダメ
Function<Double, Integer> func1 = d -> Double.hashCode(d);
Function<Double, Integer> func2 = d -> d.hashCode();
Main.java:8: エラー: 不適合な型: メソッド参照が無効です
Function<Double, Integer> func = Double::hashCode;
                                       ^
hashCodeの参照はあいまいです
 Doubleのメソッド hashCode(double)とDoubleのメソッド hashCode()の両方が一致します


使いどころは?

ラムダ式よりさらに省略ができるため、やたら使いまくると可読性が下がりまくりな気がします(;´∀`)
どこで使えばいいかというと、やはり新しく導入されたStreamAPIになります。
試験に必要な範囲は覚えるとして、実際の開発では多用せずにラムダ式にとどめておく方が良いのではないかと、個人的には思いました。


おしまいのひとこと

前回に続き、必要な知識を調べながら書いてたらものすごい時間がかかってしまいました(;´∀`)
まあ試験勉強も兼ねているのでね。早くしないと今年が終わってしまう!

一連の記事は「JavaSE8Gold」ラベルを付けていきます。
よろしければおつきあいくださいませ。

それではみなさまよきガジェットライフを(´∀`)ノ




0 件のコメント :
コメントを投稿

▼こちらの記事もどうぞ

▼ブログを気に入っていただけたらRSS登録をお願いします!
▼ブログランキング参加中!応援よろしくお願いします。

スポンサーリンク