【Java】関数型インターフェース/ラムダ式

#ラムダ式とは
* **メソッドを受け渡しするための構文**

##ラムダ式を使わない場合
* 与えられた文字列から個々の要素を取り出して前後にブラケットを加えて出力したいとき

```java
public class MethodRefUnuse {
//文字列内容をブラケット付きで出力
  public void walkArray(String[] data) {
    for (var value : data) {
      System.out.printf("[%s]\n", value);
    }
  }
}
```

```java
//文字列配列dataの内容を順に出力
public class MethodRefUnuseBasic {

  public static void main(String[] args) {
    var data = new String[] { "春はあけぼの", "夏は夜", "秋は夕暮れ" };
    var un = new MethodRefUnuse();
    un.walkArray(data); //[春はあけぼの][夏は夜][秋は夕暮れ]
  }
}
```

* ブラケットではなくカギカッコ、文字列の最初の五文字を出したい場合にいくつもwalkArrayメソッドを用意するのは大変
* **→文字列を加工する処理だけを外から引き出せるようにする**
* 似たようなメソッドを作るのは無駄！ということ

##メソッド参照
* 配列要素を処理するコードのみ切り出そう

```java:MethodRefUnuse.java
public class MethodRefUse {

//配列要素の処理方法をメソッド参照で受け取れるようにする
  //引数outputに"String型で引数を受け取り戻り値voidのメソッド参照"を渡すということ

  public void walkArray(String[] data, Output output) {
    for (var value : data) {
      output.print(value);
    }
  }
//Output型に対応したメソッド（文字列をブラケットでくくる）
  //関数型インターフェースで定義したprintメソッドの実態はaddQuoteメソッド
  static void addQuote(String value) {
    System.out.printf("[%s]\n", value);
  }
}

```
* メソッドの型を表すのは**関数型インターフェース**
    * **配下の抽象メソッドが１つ**のインターフェースのこと（SAMインターフェース）
        * 引数の個数/型、戻り値の型が決まっている必要
        * 名前は無関係で**戻り値の型**が判別のキー
        * cf: メソッドのシグニチャはメソッドの名前、引数個数/型がキー
* `@FunctionalInterface`アノテーションをつけることで対象のインターフェースが関数型であることを宣言


```java:Output.java
//String型の引数を受け取り、戻り値はvoidのメソッド型
@FunctionalInterface
public interface Output {
  void print(String str);
}
```

* メソッドにメソッド参照を渡す
    *  **クラスメソッド**：`クラス名::メソッド名`
    *  **んスタンスメソッド**：`オブジェクト変数::メソッド名`
    * **コンストラクター**：`クラス名::new`

```java:MethodRefUnuseBasic.java
public class MethodRefUseBasic {

  public static void main(String[] args) {
    var data = new String[] {"春はあけぼの", "夏は夜", "秋は夕暮れ"};
    var u = new MethodRefUse();
    //walkArrayメソッドに対してメソッド参照を引き渡す
    u.walkArray(data, MethodRefUse::addQuote);
  }
}

```

###メソッドを差し替える
* 引数outputに渡すメソッドは**関数型インターフェースで宣言された型で自由に変更可能**
* walkAttarメソッドを書き換えずメソッド参照で枠組みの機能を実装し、詳細は利用者が決める設計にできる！
    * outputを
    * `addOuote(String value)`したらブラケットで囲む
    * `addLength(String value)`したら配列の文字列長合計値を出力

```java
//文字列をカウントするCounterクラス
public class Counter {
  private int result = 0;

  public int getResult() {
    return this.result;
  }

  public void addLength(String value) {
    this.result += value.length();
  }
}
```
```java
public class CounterBasic {

  public static void main(String[] args) {
    var data = new String[] {"春はあけぼの", "夏は夜", "秋は夕暮れ"};
    var u = new MethodRefUse();
    var c = new Counter();
    //CounterクラスのaddLengthメソッドはvalueの文字列長をresultフィールドに足す
    u.walkArray(data, c::addLength);
    System.out.println(c.getResult());
  }
}
```

##ここでラムダ式が登場
* walkArrayメソッドに渡すだけでaddQuoteやaddLengthを定義するのはめんどくさい
    * 処理を引き渡すことが目的のメソッドは使い捨て
    * 一々名前をつけるのがめんどくさい。。。
* →**ラムダ式**を使う！
* メソッド定義を式（リテラル）で書くことでメソッド呼びだし文の中に直接書けてコードがスッキリ！
* **`(引数型 仮引数) -> {メソッド本体}`**
* `java.util.function`のように関数インターフェースが用意されてるので自分で準備しなくていい！

```java
//walkArrayメソッドの引数Consumerインターフェース
//ConsumerはT型の引数を受け、何らかの処理を実行する戻り値なしメソッド
import java.util.function.Consumer;

public class MethodLambda {

  public void walkArray(String[] data, Consumer<String> output) {
    for (var value : data) {
      output.accept(value);
    }
  }
}
```

```java

public class MethodLambdaBasic {

  public static void main(String[] args) {
    var data = new String[] { "春はあけぼの", "夏は夜", "秋は夕暮れ" };
    var ml = new MethodLambda();
    ml.walkArray(data, (String value) -> {
      System.out.printf("[%s]\n", value);
    });
  }
}
```

##もっと簡単にラムダ式
* 本文1文であれば{}省略可能
* return省略可能
    * `(String value) -> 
      System.out.printf("[%s]\n", value)`
* 型推論
    * `(value) -> 
      System.out.printf("[%s]\n", value)`
* 引数1個の場合
    * **`value -> 
      System.out.printf("[%s]\n", value)`**

##匿名クラスとの使い分け
* 匿名クラスで書き換える
* walkArrayメソッド第二引数はConsumer型オブジェクト
    * ラムダ式だと推論可能なものを取り除いてくれる

```java

import java.util.function.Consumer;

public class MethodLambdaBasic {

  public static void main(String[] args) {
    var data = new String[] { "春はあけぼの", "夏は夜", "秋は夕暮れ" };
    var ml = new MethodLambda();

    ml.walkArray(data, new Consumer<String>() {
      @Override
      public void accept(String value) {
        System.out.printf("[%s]\n", value);
      }
    });

     ml.walkArray(data, (String value) -> System.out.printf("[%s]\n", value));
     ml.walkArray(data, (value) -> System.out.printf("[%s]\n", value));
     ml.walkArray(data, value -> System.out.printf("[%s]\n", value));
  }
}

```

##ラムダ式コレクションフレームワークのメソッド例
### **replaceAllメソッド**
* `public void replaceAll(UnaryOperator<E> operator)`
* **引数/戻り値は同じ型を返す必要**があるので文字列リストを受け取り文字列長（int）リストを返すことはできない

```java
//先頭3文字のみ取り出し、2文字以下はそのまま出力
import java.util.ArrayList;
import java.util.Arrays;

public class CollReplace {

  public static void main(String[] args) {
    var list = new ArrayList<String>(
        Arrays.asList("Neko", "Inu", "Niwatori"));
    list.replaceAll(v -> {
      if (v.length() < 3) {
        return v;
      } else {
        return v.substring(0, 3);
      }
    });
    System.out.println(list); //[Nek, Inu, Niw]
  }
}
```


* **マップで指定されたルールで置き換える**
    * 引数functionは引数として個々のキー/値を受け取り変換した結果を返す

```java
//マップの値にキーの頭文字を付与
import java.util.HashMap;
import java.util.Map;

public class CollReplaceMap {

  public static void main(String[] args) {
    var map = new HashMap<String, String>(
      Map.of("cat", "ねこ", "dog", "いぬ", "bird", "とり"));
    map.replaceAll((k, v) -> k.charAt(0) + v);
    System.out.println(map); //{cat=cねこ, dog=dいぬ, bird=bとり}
  }
}
```

### **removeIfメソッド**
* **条件に一致した要素をリストから削除**
* 引数filterで個々の要素の受け取り条件に合っている要素だけ削除

```java
//五文字以上の文字列を全削除
import java.util.ArrayList;
import java.util.Arrays;

public class CollRemove {

  public static void main(String[] args) {
    var list = new ArrayList<String>(
      Arrays.asList("バラ", "チューリップ", "あさがお", "ヒヤシンス"));
    list.removeIf(v -> v.length() > 4);
    System.out.println(list); //[バラ", あさがお]
  }
}
```

### **computeメソッド**
* マップに処理の結果を設定
* **compute**：無条件に指定された処理結果を設定
* **computeIfPresent**：指定されたキーが存在する場合設定
* **computeIfAbsent**：指定されたキーが存在しない場合設定
* 複数の箇所からメソッドを呼びだす場合、メソッド参照の方がスッキリ

```java
import java.util.HashMap;
import java.util.Map;

//キーの先頭文字を値に付与（compute/computeIfPresent）
public class CollCompute {
  public static String trans(String key, String value) {
    return key.charAt(0) + value;
  }
//キーそのものを値に設定（computeIfAbsent）
  public static String trans(String key) {
    return key;
  }

  public static void main(String[] args) {
    var map = new HashMap<String, String>(Map.of("orange", "みかん"));

//compute
      map.compute("orange", CollCompute::trans);
      map.compute("melon", CollCompute::trans);
      System.out.println(map); //{orange＝oみかん, melon=mnull}

/*
computeIfPresent：キーが存在する場合のみ処理が実行される
    map.computeIfPresent("orange", CollCompute::trans);
    map.computeIfPresent("melon", CollCompute::trans);
    System.out.println(map); //{orange＝oみかん}
*/

/*computeIfAbsent：キーが存在しないものだけ処理
    map.computeIfAbsent("orange", CollCompute::trans);
    map.computeIfAbsent("melon", CollCompute::trans);
    System.out.println(map); //{orange＝みかん, melon=melon}
*/
  }
}

```

### mergeメソッド
* **重複キーの値を加工**
* キーが存在しない場合新しい値を設定
* `public V merge(K key, V value, BiFunction<? super V,? super V,? extends V>remap)`

```java 
//値が重複した場合に値をカンマ区切りで連結
import java.util.HashMap;
import java.util.Map;

public class CollMerge {
    public static String concat(String v1, String v2) {
        if(v2 == "") {return null;}
        return v1 + "," + v2;
    }
    public static void main(String[] args) {
        var map = new HashMap<String, String>(Map.of("cat", "みけねこ"));
        map.merge("dog", "ぽめらにあん", CollMerge::concat);
        map.merge("cat", "ぺるしゃ", CollMerge::concat);
        map.merge("dog", "ぷーどる", CollMerge::concat);
        System.out.println(map); //{dog=ぽめらにあん,ぷーどる, cat=みけねこ,ぺるしゃ}

        //結合関数(remap)がnullを返したらキーそのものが破棄
        map.merge("cat", "", CollMerge::concat);
        System.out.println(map); //{dog=ぽめらにあん,ぷーどる}
    }
}
```