Java8 ラムダ式の使い所

2018 年 11 月 19 日 by c_kuniyasua

Java8ではラムダ式という記述方法が使用できるようになりました。
ラムダ式ですが非常に強力ですが、見た目から敬遠する人も多いのでは無いでしょうか。

この使い所について、例を挙げて見たいと思います。

早速ですが、よく以下のようなコーディングを見かけたりして、間違い探しをしているような気分になります。。。
色々な事情はあると思いますが、まず関数化しましょう。

public class Todo{
    public void todo(Data data){
       //Aについて
       if(data.a == null){
          data.a = 0;
          log.info("set data.a = 0");
       }
       if(data.a > 9){
          data.a = 9;
          log.info("set data.a = 9");
       }

       //Bについて
       if(data.b == null){
          data.b = 0;
          log.info("set data.b = 0");
       }
       if(data.b > 9){
          data.b = 9;
          log.info("set data.b = 9");
       }

       //Cについて
       if(data.c == null){
          data.c = 0;
          log.info("set data.c = 0");
       }
       if(data.c > 9){
          data.c = 9;
          log.info("set data.c = 9");
       }
       //...
       //Zについて
    }
}

きれいになりましたね。todoの中身がスッキリして見通しがよくなりました。
似たような処理が沢山あると、業務処理の内容が埋もれてしまい、結局何がしたいのか分からなくなってしまいます。

public class Todo{
    public void todo(Data data){
       data.a = normalize(data.a, "data.a");//Aについて
       data.b = normalize(data.b, "data.b");//Bについて
       data.c = normalize(data.c, "data.c");//Cについて
       //...
       //Zについて
    }

    private Integer normalize(Integer target, String label){
       Integer result = target;
       if(result == null){
          result = 0;
          log.info("set " + label + " = 0");
       }
       if(result > 9){
          result = 9;
          log.info("set " + label + " = 9");
       }
       return result;
    }
}

さて、まだラムダ式はまだ登場していませんね。
先程の関数化ですが、実務ではそんなに上手くいく訳がないというのが現実ではないでしょうか?
例えば追加の仕様で
・「b」だけは奇数は不可。奇数の場合は1を減算した偶数にする。
というものがあったらどうでしょうか?

以下は、追加の処理をラムダ式で受け取れる様に対応した例となります。

public class Todo{
    public void todo(Data data){
       data.a = normalize(data.a, "data.a", null);//Aについて
       data.b = normalize(data.b, "data.b", v -> v % 2 == 1 ? v-1 : null);//Bについて
       data.c = normalize(data.c, "data.c", null);//Cについて
       //...
       //Zについて
    }
    private Integer normalize(Integer target, String label, Function<Integer, Integer> optionally){
       Integer result = target;
       if(result == null){
          result = 0;
          log.info("set " + label + " = 0");
       }
       if(result > 9){
          result = 9;
          log.info("set " + label + " = 9");
       }
       if(optionally != null){
          Integer work = optionally.apply(result);
          if(work != null){
              result = work;
              log.info("set " + label + " = " + result);
          }
       }
       return result;
    }
}

まず、normalize()関数側ではoptionally引数に指定されていれば、追加の処理を実行するように修正しています。
呼び出し元(todo()関数)では、「b」の場合だけ、第3引数にラムダ式で追加の処理を定義して独自の仕様に対応するようにしています。

この様に呼出元で、処理を定義して渡せる(*1)ので、これまで例外ばかりで共通化が難しかった処理なども簡潔に定義することができるようになります。

*1:これまでもインタフェースを定義して、無名クラスで実装を渡せば同じことが可能ですが、ラムダ式として定義できることでより手軽に利用できるようになりました。

他の対応方法として、オーバーロードを追加して対応する場合は以下のようになります。
今後、例外が増えた場合が少し心配ですね。

public class Todo{
    public void todo(Data data){
       data.a = normalize(data.a, "data.a");//Aについて
       data.b = normalize(data.b, "data.b", true);//Bについて
       data.c = normalize(data.c, "data.c");//Cについて
       //...
       //Zについて
    }
    private Integer normalize(Integer target, String label){
       Integer result = target;
       if(target == null){
          result = 0;
          log.info("set " + label + " = 0");
       }
       if(result > 9){
          result = 9;
          log.info("set " + label + " = 9");
       }
       return result;
    }
    private Integer normalize(Integer target, String label, boolean isRequiredEven){
       Integer result = this.normalize(target, label);
       if( isRequiredEven && (result % 2 == 1) ){
          result = result - 1;
          log.info("set " + label + " = " + result);
       }
       return result;
    }
}

コーディング方法は、ケースバイケースでいずれの方法にも正解はないと思いますが、よりベストな選択ができるように色々な方法を知っておく事が大切だと思います。

TrackBack