0
0

More than 3 years have passed since last update.

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

Posted at

ラムダ式とは

  • メソッドを受け渡しするための構文

ラムダ式を使わない場合

  • 与えられた文字列から個々の要素を取り出して前後にブラケットを加えて出力したいとき
public class MethodRefUnuse {
//文字列内容をブラケット付きで出力
  public void walkArray(String[] data) {
    for (var value : data) {
      System.out.printf("[%s]\n", value);
    }
  }
}
//文字列配列dataの内容を順に出力
public class MethodRefUnuseBasic {

  public static void main(String[] args) {
    var data = new String[] { "春はあけぼの", "夏は夜", "秋は夕暮れ" };
    var un = new MethodRefUnuse();
    un.walkArray(data); //[春はあけぼの][夏は夜][秋は夕暮れ]
  }
}
  • ブラケットではなくカギカッコ、文字列の最初の五文字を出したい場合にいくつもwalkArrayメソッドを用意するのは大変
  • →文字列を加工する処理だけを外から引き出せるようにする
  • 似たようなメソッドを作るのは無駄!ということ

メソッド参照

  • 配列要素を処理するコードのみ切り出そう
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);
  }
}

  • メソッドの型を表すのは関数型インターフェース
    • 配下の抽象メソッドが1つのインターフェースのこと(SAMインターフェース)
      • 引数の個数/型、戻り値の型が決まっている必要
      • 名前は無関係で戻り値の型が判別のキー
      • cf: メソッドのシグニチャはメソッドの名前、引数個数/型がキー
  • @FunctionalInterfaceアノテーションをつけることで対象のインターフェースが関数型であることを宣言
Output.java
//String型の引数を受け取り、戻り値はvoidのメソッド型
@FunctionalInterface
public interface Output {
  void print(String str);
}
  • メソッドにメソッド参照を渡す
    • クラスメソッドクラス名::メソッド名
    • んスタンスメソッドオブジェクト変数::メソッド名
    • コンストラクタークラス名::new
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)したら配列の文字列長合計値を出力
//文字列をカウントするCounterクラス
public class Counter {
  private int result = 0;

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

  public void addLength(String value) {
    this.result += value.length();
  }
}
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のように関数インターフェースが用意されてるので自分で準備しなくていい!
//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);
    }
  }
}

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型オブジェクト
    • ラムダ式だと推論可能なものを取り除いてくれる

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)リストを返すことはできない
//先頭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は引数として個々のキー/値を受け取り変換した結果を返す
//マップの値にキーの頭文字を付与
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で個々の要素の受け取り条件に合っている要素だけ削除
//五文字以上の文字列を全削除
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:指定されたキーが存在しない場合設定
  • 複数の箇所からメソッドを呼びだす場合、メソッド参照の方がスッキリ
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)
//値が重複した場合に値をカンマ区切りで連結
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=ぽめらにあん,ぷーどる}
    }
}
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0