3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

~今さらながらJavaで関数型プログラミングを学習してみた~(第2部)

Last updated at Posted at 2021-01-21

「関数型プログラミングとJava8での取り組みと追加API説明」を中心テーマとした第1部はすでに投稿されています。
 第1部へのリンク

「分かりにくいmap,flatMap,collectの追加説明と知っておくと便利なCollector,Collections,Arrays」を中心テーマとした第3部はすでに投稿されています。
 第3部へのリンク

はじめに

第1部では関数型プログラミング自体の概説と、Java8で新たに取り入れられたAPI+そのサンプルを中心に紹介しました。
本第2部では「実際Javaシステム開発においてどの程度関数型プログラミングを取り込むべきなのか?」を中心テーマとして少し現実的なサンプルも交えて検討していきたいと思います。
あわせて関数型プログラミングの説明でよく用いられている「パイプライン、高階関数、カリー化」という用語についてもJavaベースでの説明を行っていきます。
なお、内容には主観にもとづく部分や、個人的指向が入ったプログラムスタイルになっている場合がありますので了承お願い致します。
関数プログラミング特有の語句については第1部の後尾部分に理解した範囲での簡単な説明を記述していますので参考にして下さい。

関数型プログラミングにおける代表的手法

中心テーマに入る前に、まず関数型プログラミング説明においてよく用いられている代表的手法についてJavaベースで説明しておきたいと思います。
代表的手法としてパイプライン、高階階数、カリー化(部分適用)を対象としました。

(1) パイプライン
Javaでの関数型プログラミングにおけるパイプライン処理とは一般的にはStreamAPIでのパイプライン処理を意味します。
このパイプライン処理はストリームパイプラインとも呼ばれています。
ストリームパイプラインは中間操作(0個以上)と終端操作を連結して構成した処理となっています。
以下のサンプルではListをstream化し、その後filter(条件に一致したデータを抽出)処理やsort処理を行って、終端処理として結果戻り値であるList作成を作成したり、各要素の出力を行っています。
StreamAPIではこれらの一連の処理を014、016、018のようにパイプライン形式で1行に記述することが可能になっています。
この方法はJavaにおける関数型プログラミングスタイルでは一般的によく使用されます。

PipelineTest.java
001 package functiontest.unit;
002 import java.util.Arrays;
003 import java.util.Comparator;
004 import java.util.List;
005 import java.util.stream.Collectors;
    /**
    * Streamパイプラインテストクラス
    */
006 public class PipelineTest {
007 public static void main(String[] args) {
008   List<Food> foods = Arrays.asList(new Food[] {new Food("sibuya", "ラーメン", 1700), new Food("sibuya", "チャーハン", 800),
009     new Food("sibuya", "餃子", 500), 	new Food("sinjuku", "スパゲティ", 1000), new Food("sinjuku", "オムライス", 900),
010     new Food("sinjuku", "ステーキ", 2500)});
011   execute(foods);
012 }
    /**
    *パイプラインテスト実行
    */
013 public static void execute(List<Food> foods) {
      //フィルター処理→リスト化
014   List<Food> filteredList = foods.stream().filter(fobj -> fobj.getStore().startsWith("sib")).collect(Collectors.toList());
015   System.out.println(filteredList);
      //ソート処理→リスト化
016   List<Food> sorttedList = foods.stream().sorted(Comparator.comparing(Food::getPrice)).collect(Collectors.toList());
017   System.out.println(sorttedList);
      //フィルター処理+ソート処理→要素出力処理
018   foods.stream().filter(fobj -> fobj.getPrice() >= 1000).sorted(Comparator.comparing(Food::getName)).forEach(System.out::println);
019 }
020 }
Food.java
    /**
    *Food情報格納クラス
    */
001 package functiontest.unit;
002 public class Food {
003   String store;
004   String name;
005   int price;
006 Food( String store, String name, int stock){
007   this.store = store;
008   this.name=name;
009   this.price = stock;
010 }
011 public String getStore() {
012   return store;
013 }
014 public String getName() {
015   return name;
016 }
017 public int getPrice() {
018   return price;
019 }

020 }

(2) 高階関数
高階関数とは他の関数を引数あるいは結果として返すことができる関数(メソッド)のことです。
Java8ではmap, flatMap, filter, reduce, forEach, anyMatch, allMatch関数などが標準で用意されています。
引数で設定される関数の処理内容についてはラムダ式を用いて実装・記述することができます。
また、自作関数インターフェイスを作成すれば高階関数を自作することもできます。
高階関数の目的・メリットは関数の一部処理を引数(関数)で渡すことによって特定処理(引数(関数)以外の部分)を共通化して整理することができるという点です。
つまり、大枠の処理を関数として共通化しておき、具体的な一部処理を引数(関数)で渡すということができるようになります。
カリー化もこの高階関数を利用して実現しています。
以下に6つの高階関数使用サンプル(最後のサンプルは対象外)を提示して概説致します。
・pickupAndCountメソッド(015~027):指定した範囲条件にマッチした値の件数を算出しています。
 016~024が命令型スタイルでのプログラミングで、025~026が関数型スタイルでのプログラミングとなっています。
 026でfilter高階関数を使用してラムダ式で条件を指定しています。
・reduceSumメソッド(028~032):配列要素の合計値を算出します。
 030でreduce高階関数を使用して合計値を算出しています。
・calSquareメソッド(033~038):二乗値を算出します。
 035で二乗値算出式を定義して標準Functionインターフェイスに設定しています。
 037のmapメソッドではこの標準Functionインターフェイスを引数として渡しています。
・calFiveTimesUsingInterfaceメソッド(039~042):標準Functionインターフェイスを使用して5倍値を算出しています。
 040でgetFiveTimesメソッドを使用した5倍値算出式を定義して標準Functionインターフェイスに設定しています。
 041でこのFunctionインターフェイスのapplyメソッドで5倍値を算出しています。
・calFiveTimesUsingLambdaメソッド(043~052):ラムダ式を使用して5倍値を算出しています。
 044~046でexecuteメソッドに渡す引数としてラムダ式(5倍算出式)を定義しています。
 049~052でexecuteメソッド内容を定義し引数の標準Functionインターフェイスでのapplyメソッドを実行しています。
・calIntegerAndCompareメソッド(053~069):2倍と5倍の値を比較し大きい方の値を抽出しています。
 054&055で2つのメソッドを使用した2倍&5倍値算出式を定義して標準Functionインターフェイスに設定しています。
 056でcalAndCompareメソッドを実行して2倍&5倍値の比較結果を取得しています。
 059~063でcalAndCompareメソッドの処理内容を定義しています。
 064~069でgetDoubleメソッド及びgetFiveTimesメソッドの具体的処理内容を定義しています。
・concatThreeStringメソッド(070~077):自作関数インターフェイスを使用して3つの文字列を結合しています。
 071で3文字列結合する自作関数インターフェイス(メソッド名はconcatString)を定義しています。
 072でconcatStringメソッド3文字列結合を実行しています。

HigherOrderFunctionTest.java
001 package functiontest.unit;
002 import java.util.Arrays;
003 import java.util.List;
004 import java.util.function.Function;
    /**
    * 高階関数テストクラス
    */
005 public class HigherOrderFunctionTest {

006 public static void main(String[] args) {
007   pickupAndCount();
008   reduceSum();
009   calSquare();
010   calFiveTimesUsingInterface(2);
011   calFiveTimesUsingLambda(1);
012   calIntegerAndCompare(3);
013   concatThreeString();
014 }
    /**
    * 高階関数(filter)を使用したリスト内における数値範囲条件にマッチした件数算出
    */
015 private static void pickupAndCount() {
016   List<Integer> list = Arrays.asList(new Integer[] {38, 20, 40, 32, 36});
017   System.out.println("命令型スタイルでのプログラミング");
018   int count = 0;
019   for (int num : list) {
020     if (num ==  20 || (num >= 30 && num <= 39)) {
021       count++;
022     }
023   }
024   System.out.println("命令型count:" + count);
      //
025   System.out.println("高階関数を使用したプログラミングスタイル(StreamAPI)");
026   System.out.println("関数型スタイルcount:" + list.stream().filter(n -> n == 20 || (n >= 30 && n <= 39)).count());
027 }
    /**
    * 高階関数(reduce)を使用した配列要素合計値算出
    */
028 public static void reduceSum() {
029   List<Integer> nums = Arrays.asList(new Integer[] {0, 1, 2, 3, 4});
030   int sum = nums.stream().reduce(0, (t, x) -> t + x);
031   System.out.println("関数型スタイルsum:" + sum);
032 }
    /**
    * 高階関数(map)を使用した二乗値算出
    */
033 public static void calSquare(){
034   List<Integer> nums = Arrays.asList(new Integer[]{0, 1, 2, 3, 4});
      //Functionインターフェイスを定義
035   Function<Integer, Integer> square = (x) -> { return x * x; };
      //StreamAPIのmapを使用して二乗算出
036   System.out.println("関数型スタイルsquare:");
037   nums.stream().map(square).forEach(System.out::println);
038 }
    /**
    * Functionインターフェイスを使用した5倍値算出
    */
039 public static void calFiveTimesUsingInterface(Integer value) {
040   Function<Integer, Integer> fivetimes = (x) -> { return getFiveTimes(x);};
041   System.out.println("関数型スタイルfivetimes_interface:" + fivetimes.apply(value));
042 }
    /**
    * 高階関数(execute)を使用した5倍値算出
    */
043 public static void calFiveTimesUsingLambda(Integer value) {
044   Integer fivetimes = execute(value, t -> {
045     return getFiveTimes(value);
046   });
047   System.out.println("関数型スタイルfivetimes_lambda:" + fivetimes);
048 }
    /**
    * 引数の関数インターフェイス(ラムダ式形式で定義)を実行するメソッド
    */
049 public static <R> R execute(Integer value, Function<Integer, R> fnc) {
050   R rtnval = fnc.apply(value);
051   return rtnval;
052 }
    /**
    * 高階関数(calAndCompare)を使用した2倍と5倍の値を比較し大きい値を抽出
    */
053 public static void calIntegerAndCompare(Integer value) {
054   Function<Integer, Integer> times1 = (x) -> { return getDouble(x);};
055   Function<Integer, Integer> times2 = (x) -> { return getFiveTimes(x);};
056   int rtnval = calAndCompare(value, times1, times2);
057   System.out.println("関数型スタイル(値比較)rtnval:" + rtnval);
058 }
    /**
    * 引数のFunctionインターフェイスを実行し値を比較するメソッド
    */
059 public static Integer calAndCompare(Integer value, Function<Integer, Integer> fnc1, Function<Integer, Integer> fnc2) {
060   int rtnval1 = fnc1.apply(value);
061   int rtnval2 = fnc2.apply(value);
062   return Math.max(rtnval1, rtnval2);
063 }
    /**
    * 具体的な処理を実行するメソッド(値を2倍する)
    */
064 public static Integer getDouble(Integer value) {
065   return value * 2;
066 }
    /**
    * 具体的な処理を実行するメソッド(値を5倍する)
    */
067 public static Integer getFiveTimes(Integer value) {
068   return value * 5;
069 }
    /**
    * 自作インターフェースを使用した3つの文字列を結合
    */
070 public static void concatThreeString() {
      //IOriginalFuncの定義と実行
071   IOriginalInterface originalInterface = (str1, str2, str3) -> {return str1+str2+str3;};
072   System.out.println(originalInterface.concatString("AAA", "BBB", "CCC"));
073 }
    //自作の関数型インターフェース
074 @FunctionalInterface
075 public interface IOriginalInterface {
      // String三つを引数に取る関数を定義
076   public String concatString(String str1, String str2, String str3);
077 }
078 }

(3) カリー化
関数型プログラミングにおいて理解しにくい単語の1つが「カリー化」ですが、基本だけでも理解すればそれほど難しくはありません。
カリー化とは複数の引数を受け取って処理する関数を、最初の引数を受け取って処理し、「残りの引数を受け取って処理する関数」を返す関数に変換することをいいます。
簡単簡略に言えば、関数を引数ごとに分解・分割して、それらを連続(ネスト)して処理することを「カリー化して処理すること」となります。
具体的に記述すると、引数3個の関数f(t, u, v) 関数があるとすると、この関数にt引数だけ渡して処理し、結果として引数uとvを持つg(u, v) 関数を返すこととなります。
これによって単引数を持つ関数の連続(ネスト)によって複数引数の関数を表現・記述することが可能になります。
ちなみに、純粋関数型言語の実質的標準であるHaskellにおいては原則複数引数の関数というものは定義できず、引数は1つとなっているようです。
ただ形式的(糖衣構文)には複数引数の設定は可能になっているようでこの場合には自動的にカリー化されて単引数関数を用いて処理されるようです。
(筆者はHaskell未使用なので確定的な記述はできません。)
ではなぜこのようなカリー化を行うかということですが、「カリー化」の目的は「部分適用」や「関数合成」を行う(やりやすくする)ためとなっています。
(ただ部分適用にカリー化が必須という訳ではありません。)
つまり、関数型プログラミングで関数の引数を1つにすることを原則・基本とするのは、「部分適用」・「関数合成」等の関数型プログラミング特有(限定という意味ではありません。)の処理・方法のためとも言えます。
「関数合成」は文字通り関数を合成して新しい関数を作るものです。
「部分適用」とは複数引数の関数の一部を固定化して引数を減らした関数を作ることを言い、これにより複数引数関数における不必要な処理について省略することが可能となります。
どちらもJava8以降で対応することが可能で、「関数合成」はFunctionインターフェイスでの2メソッドcomposeとandThenを利用することとなります。
ただ、開発システムのどのような処理・機能に対して、どのようにこの特有の処理・方法を適用していくかについては、純粋関数型プログラミング範疇に入り本記事の範疇を超えるのでここでは割愛としますので、他参考書を参照して下さい。
なお、以下ではJavaにおいて比較的「関数合成」よりはどちらかというと利用可能性があると思われる「カリー化」+「部分適用」についてのサンプルと説明を記述します。
・mainメソッド(001~040):カリー化及び部分適用テストのメインメソッド。
 007~009:1つの引数数値が1桁の場合1を返し、それ以外は-1を返す(カリー化をしていない)。
 010~012:2つの引数が0以上でs<eの場合1を返し、それ以外は-1を返す(カリー化をしていない)。
 013~018:2つの引数が0以上でs<eの場合1を返し、それ以外は-1を返す(カリー化して部分適用)。
  013で010で定義したITwoParameter(BiFunction)インターフェイスをカリー化しています。
  046~050でカリー化を行うcurryingメソッドを定義しています。
  014で1つ目の引数を固定してIOneParameter(Function)インターフェイスを取得し、これを使用して015そして017で2つ目の引数を設定して比較結果を求めています。
 019~022:013~018と同じ処理内容ですが一時的なインターフェイスを取得せずに実行しています。
 023~030:3つの引数が0以上でs<e<vの場合1を返し、それ以外は-1を返す。
  023~025でカリー化を行い、026で2つの引数を固定してIOneParameter(Function)インターフェイスを取得しています。
  027そして029で3つ目の引数を設定して比較結果を求めています。
 031~034:023~030と同じ処理内容ですが一時的なインターフェイスを取得せずに実行しています。
 035~040:2引数ケースと同じ処理を命令型複数引数メソッドで処理した場合とメンバー変数を使用した場合(部分適用に類似する)を記述。
 041~056:引数1,2,3個の関数インターフェイスを定義。
 057~059:メンバー変数の設定メソッド。
 060~069:引数が1つ及び2つの命令型比較メソッド。

CurryingTest.java
001 package functiontest.unit;
002 import java.util.function.BiFunction;
003 import java.util.function.Function;
    /**
    * カリー化テストクラス
    */
004 public final class CurryingTest {
005 private static Integer sint;
006 public static void main(String[] args) {
      //☆☆引数が1つのケース
      //引数数値が1桁の場合1を返し、それ以外は-1を返す(カリー化をしていないケース)
007   IOneParameter<Integer, Boolean> p1exe = (s) -> {if (-1 < s && 10 > s) {return true;} else {return false;}};
008   Boolean result = p1exe.apply(3);
009   System.out.println("IOneParameter_result:" + result);
      //☆☆引数が2つのケース
      //引数が0以上でs<eの場合1を返し、それ以外は-1を返す(カリー化をしていないケース)
010   ITwoParameter<Integer, Integer, Boolean> p2exe = (s, e) -> {if (-1 < s && -1 < e && s < e) {return true;} else {return false;}};
011   Boolean cmpResult1 = p2exe.apply(1, 2);
012   System.out.println("TwoParameter_cmpResult1:" + cmpResult1);
      //カリー化を実施し部分適用をしたケース(2ケース)
013   IOneParameter<Integer, IOneParameter<Integer, Boolean>> firstP2exe = p2exe.currying();
014   IOneParameter<Integer, Boolean> secondP2exe = firstP2exe.compare(2);
015   Boolean cmpResult2a = secondP2exe.compare(1);
016   System.out.println("TwoParameter_cmpResult2a:" + cmpResult2a);
017   Boolean cmpResult2b = secondP2exe.compare(3);
018   System.out.println("ITwoParameter_cmpResult2b:" + cmpResult2b);
      //カリー化を実施しインターフェイスを取得せずに実行したケース
019   IOneParameter<Integer, IOneParameter<Integer, Boolean>> curryingP2exe = s -> e -> {
020     if (-1 < s && -1 < e && s < e) {return true;} else {return false;}};
021   Boolean cmpResult2c = curryingP2exe.compare(1).compare(2);
022   System.out.println("CurryingTwoParameter_cmpResult2c:" + cmpResult2c);
      //☆☆引数が3つのケース
      //引数が0以上でs<e<vの場合1を返し、それ以外は-1を返す(カリー化をしないケース)
023   IThreeParameter<Integer, Integer, Integer, Boolean> p3exe = (s, e, v) -> {
024     if (-1 < s && -1 < e && -1 < v && s < e && e < v) {return true;} else {return false;}};
      //カリー化を実施し部分適用をしたケース(2ケース)
025   IOneParameter<Integer, IOneParameter<Integer, IOneParameter<Integer, Boolean>>> firstP3exe = p3exe.currying();
026   IOneParameter<Integer, Boolean> secondP3exe = firstP3exe.compare(1).compare(2);
027   Boolean cmpResult3a = secondP3exe.compare(1);
028   System.out.println("ThreeParameter_cmpResult3a:" + cmpResult3a);
029   Boolean cmpResult3b = secondP3exe.compare(3);
030   System.out.println("IThreeParameter_cmpResult3b:" + cmpResult3b);
      //カリー化を実施しインターフェイスを取得せずに実行したケース
031   IOneParameter<Integer, IOneParameter<Integer, IOneParameter<Integer, Boolean>>> curryingP3exe = s -> e -> v -> {
032     if (-1 < s && -1 < e && -1 < v && s < e && e < v) {return true;} else {return false;}};
033   Boolean cmpResult3c = curryingP3exe.compare(1).compare(2).compare(3);
034   System.out.println("CurryingThreeParameter_cmpResult4:" + cmpResult3c);
      //☆☆引数が2つのケース
      //命令型(オブジェクト指向)タイプで実行したケース
035   Boolean cmpResult4a = compareByObject (2, 1);
036   setSint(2);
037   Boolean cmpResult4b = compareByObject (3);
038   System.out.println("ObjectTwoParameter_cmpResult4a:" + cmpResult4a);
039   System.out.println("ObjectTwoParameter_cmpResult4b:" + cmpResult4b);
040 }
    /**
    * 引数が1つのFunction型インターフェイス(compareメソッド定義)
    */
041 interface IOneParameter<T, R> extends Function<T, R> {
042   default R compare(T t) {
043     return apply(t);
044   }
045 }
    /**
    * 引数が2つのBiFunction型インターフェイス(curryingメソッドでカリー化定義)
    */
046 interface ITwoParameter<T, U, R> extends BiFunction<T, U, R> {
047   default IOneParameter<T, IOneParameter<U, R>> currying() {
048     return p1 -> p2 -> apply(p1, p2);
049   }
050 }
    /**
    * 引数が3つの関数型インターフェイス(curryingメソッドでカリー化定義)
    */
051 interface IThreeParameter<T, U, V, R> {
052   R apply(T t, U u, V v);
053   default IOneParameter<T, IOneParameter<U, IOneParameter<V, R>>> currying() {
054     return p1 -> p2 -> p3 -> apply(p1, p2, p3);
055   }
056 }
    /**
    * sintメンバー変数の設定
    */
057 public static void setSint(Integer sint) {
058   CurryingTest.sint = sint;
059 }
    /**
    * 引数が1つの命令型(オブジェクト指向)タイプでの比較
    */
060 public static Boolean compareByObject (Integer e) {
061   return compareByObject (CurryingTest.sint, e);
062 }
    /**
    * 引数が2つの命令型(オブジェクト指向)タイプでの比較
    */
063 private static Boolean compareByObject (Integer s, Integer e) {
064   if (-1 < s && -1 < e && e > s) {
065     return true;
066   } else {
067     return false;
068   }
069 }

070 }

オブジェクト指向(命令型)と関数型プログラミングの比較・相違

次に、Javaオブジェクト指向(命令型)と関数型プログラミングの基本的な相違を簡単に説明してみたいと思います。
オブジェクト指向は基本的にデータと操作(関数)を一体化(クラス化)してその独立性をできるだけ高めシステムの堅牢性を向上させるという方法です。
クラスが完全に独立できればクラス単位という視点でみれば副作用排除ができるということになります(正確に言えば違いますが)。
しかし、一般的にはそのようなケースはまれで、そもそもクラス(概念)間でメッセージをやり取りするというオブジェクト指向本来の指向とは異なるため、全面的な副作用排除は難しいと言えます。
関数型プログラミングは関数(操作)単位での副作用排除(参照透過性の保証)を目指し、関数(操作)間で共有する変数をなくし、関数(操作)内での代入文も無くし、目的・出力(このデータを用いてこの出力を得る)を示して、それを実現する手続きはシステム、ライブラリやユーティリティ等に任せていくという方法であり、データと操作の一体化という指向ではなく、逆にどちらかというとデータと関数(操作)は分離していく傾向にあります。
つまりオブジェクト指向と関数型プログラミングはベースとしては相反に近い関係にあるとも言えることになり、オブジェクト指向ベースによるシステム開発に関数型プログラミングを全面的に近い形で取り入れることはほぼ困難であり、あくまで部分的・表層的な取り入れということになります。

現実的サンプルを使用したオブジェクト指向プログラミング→関数型プログラミング例

ではどの程度関数型プログラミングを取り入れることができるか、あるいは取り入れるべきなのかという事が焦点となります。
このことを考察・推察するために世の中で一般的に提示されている、「関数型プログラミングにすればこんなにコーディング量は少なくなります」、というようなごく簡単なサンプルではなく、少しだけ現実的なサンプルをオブジェクト指向(命令型)と関数型プログラミングの両方で実装して比較することにしました。
なおJava関数型プログラミングでは純粋に副作用を排除することは困難なので、
(1)メソッド外の変数を使用・代入しない(ただしコンストラクタは除外)。
(2)for文を使用しない。
(3)メソッド内でのローカル変数の代入は可能とするが極力少なくする。
という条件で作成してできるだけ副作用を少なくしています。
この現実的なサンプルではパイプラインや高階関数が複数使用されています。
現実的なサンプルは単純な商品在庫管理でメインクラス、在庫管理クラス、商品グループ別合計数及び平均値算出用ユーティリティクラスの3クラスで構成されています。
機能としては商品登録、在庫数更新(入庫/出庫)、商品グループ別合計数及び平均値算出があります。
"Object"と付加したクラスがオブジェクト指向(命令型)で、"Function"と付加したクラスが関数型プログラミングとなります。
StockObjectSampleMain:オブジェクト指向(命令型) 商品在庫管理メインクラス
StockObjectSample:オブジェクト指向(命令型) 商品在庫管理クラス
UtilityObjectSample:オブジェクト指向(命令型) 商品在庫管理用ユーティリティ
StockFunctionSampleMain:関数型プログラミング 商品在庫管理メインクラス
StockFunctionSample:関数型プログラミング 商品在庫管理クラス
UtilityFunctionSample:関数型プログラミング 商品在庫管理用ユーティリティ
処理内容はAタイプ商品の登録→在庫数チェック→入庫、Bタイプ商品の登録→在庫数チェック→入庫、全商品在庫数出力、グループ別合計数及び平均値出力としています。
グループ別合計数及び平均値算出はユーティリティクラスで算出しています。
以下5機能(メソッド)については処理形式が類似しているので標準関数インターフェイスを各々定義してexecuteFunctionメソッド(高階関数)を使用して関数引渡しで処理しています。
標準関数インターフェイスはStockFunctionSample.javaの038~076、executeFunctionメソッドは091~104で確認できます。
標準関数インターフェイスはコンストラクタで設定しています。
・商品確認:指定商品が登録されているか確認:checkGoodsメソッド
・在庫追加:入庫:stockメソッド
・在庫減少:出庫:shipメソッド
・在庫確認:指定数の在庫があるかの確認:checkStockメソッド
・商品・在庫追加:指定商品を登録し在庫数を設定:addGoods+stockメソッド
UtilityFunctionSample.javaではグループ別合計数及び平均値出力をStreamAPI(filter、flatmap、reduce、forEachメソッド使用)を用いて算出しています。

StockObjectSampleMain.java
001 package functiontest.stock;
    /**
    * オブジェクト指向(命令型):商品在庫管理メインクラス
    * 	在庫情報:商品名、在庫数、商品説明の3要素で構成
    * 商品名:"Goods"+商品グループ名(1文字)+連番で構成
    */
002 public class StockObjectSampleMain {
003   private static final String[] goodsNameA = {"GoodsA1", "GoodsA2", "GoodsA3"};
004   private static final String[] goodsNameB = {"GoodsB1", "GoodsB2", "GoodsB3"};
005   private static final int[] goodsStockA = {1, 2, 3};
006   private static final int[] goodsStockB = {1, 2, 3};
007   private static final String[] goodsDescriptionA = {"商品A1", "商品A2", "商品A3"};
008   private static final String[] goodsDescriptionB = {"商品B1", "商品B2", "商品B3"};
009 public static void main(String[] args) {
010   System.out.println("開始");
      //初期化
011   StockObjectSample ss = initialize();
      //在庫追加
012   ss.stock(goodsNameA[0], goodsStockA[0]);
013   ss.stock(goodsNameA[1], goodsStockA[1]);
014   ss.stock(goodsNameA[2], goodsStockA[2]);
      //商品確認
015   System.out.println("商品確認(GoodsA1):" + ss.checkGoods(goodsNameA[0]));
      //在庫確認
016   System.out.println("在庫確認(GoodsA2):" + ss.checkStock(goodsNameA[1], 2));
      //在庫数確認
017   System.out.println("在庫数確認(GoodsA3):" + ss.getStock(goodsNameA[2]));
      //商品・在庫追加
018   update(goodsNameB[0], goodsStockB[0], goodsDescriptionB[0]).addGoods();
019   update(goodsNameB[1], goodsStockB[1], goodsDescriptionB[1]).addGoods();
020   update(goodsNameB[2], goodsStockB[2], goodsDescriptionB[2]).addGoods();
021   update(goodsNameB[0], goodsStockB[0]).stock();
022   update(goodsNameB[1], goodsStockB[1]).stock();
023   update(goodsNameB[2], goodsStockB[2]).stock();
      //在庫リスト出力
024   ss.outputStock();
      //グループ別在庫数出力
025   ss.outputStockByGroup();
026   System.out.println("終了");
027 }
    /**
    * 初期化
    */
028 private static StockObjectSample initialize() {
029   StockObjectSample ss = new StockObjectSample();
030   ss.addGoods(goodsNameA[0], 0, goodsDescriptionA[0]);
031   ss.addGoods(goodsNameA[1], 0, goodsDescriptionA[1]);
032   ss.addGoods(goodsNameA[2], 0, goodsDescriptionA[2]);
033   return ss;
034 }
    /**
    * 商品在庫情報の更新
    */
035 private static StockObjectSample update(String name, int num, String des) {
036   StockObjectSample ss = new StockObjectSample(name, num, des);
037   return ss;
038 }
    /**
    * 商品在庫情報の更新
    */
039 private static StockObjectSample update(String name, int num) {
040   StockObjectSample ss = new StockObjectSample(name, num);
041   return ss;
042 }
043 }
StockObjectSample.java
001 package functiontest.stock;
002 import java.util.ArrayList;
003 import java.util.List;
004 import functiontest.utility.UtilityObjectSample;
    /**
    * オブジェクト指向(命令型):商品在庫管理クラス
    * 	在庫情報:商品名、在庫数、商品説明の3要素で構成
    * 商品名:"Goods"+商品グループ名(1文字)+連番で構成
    */
005 public class StockObjectSample {
006   private static List<String> goodsNameList = new ArrayList<String>();
007   private static List<Integer> numberStockList = new ArrayList<Integer>();
008   private static List<String> descriptionList = new ArrayList<String>();
009   private String goodsName;
010   private Integer numberStock;
011   private String description;
    /**
    * コンストラクタ
    */
012 public StockObjectSample() {
013   numberStock = 0;
014 }
    /**
    * コンストラクタ
    */
015 public StockObjectSample(String name, int num, String des) {
016   goodsName = name;
017   numberStock = num;
018   description = des;
019 }
    /**
    * コンストラクタ
    */
020 public StockObjectSample(String name, int num) {
021   goodsName = name;
022   numberStock = num;
023 }
    /**
    * 商品登録
    */
024 public void addGoods() {
025   addGoods(goodsName, numberStock, description);
026 }
    /**
    * 商品登録
    */
027 public void addGoods(String name, int num, String des) {
028   if (-1 < checkAdditionalGoods(name)) {
029     goodsNameList.add(name);
030     numberStockList.add(num);
031     setDescription(des);
032   }
033 }
    /**
    * 入庫
    */
034 public int stock() {
035   return stock(goodsName, numberStock);
036 }
    /**
    * 入庫
    */
037 public int stock(String name, int num) {
038   int index = checkGoods(name);
039   int sval = -1;
040   if (-1 < index) {
041     sval = numberStockList.get(index).intValue() + num;
042     numberStockList.set(index, sval);
043   }
044   return sval;
045 }
    /**
    * 出庫
    */
046 public void ship() {
047   ship(goodsName, numberStock);
048 }
    /**
    * 出庫
    */
049 public int ship(String name, int num) {
050   int index = checkGoods(name);
051   int sval = -1;
052   if (-1 < index) {
053     sval = numberStockList.get(index).intValue() - num;
054     numberStockList.set(index, sval);
055   }
056   return sval;
057 }
    /**
    * 在庫数チェック
    */
058 public boolean checkStock() {
059   return checkStock(goodsName, numberStock);
060 }
    /**
    * 在庫数チェック
    */
061 public boolean checkStock(String name, int num) {
062   boolean result = true;
063   int index = checkGoods(name);
064   if (-1 < index) {
065     if (num > numberStockList.get(index).intValue()) {
066       result = false;
067     }
068   }
069   return result;
070 }
    /**
    * 在庫数取得
    */
071 public int getStock() {
072   return getStock(goodsName);
073 }
    /**
    * 在庫数取得
    */
074 public int getStock(String name) {
075   int result = 0;
076   int index = checkGoods(name);
077   if (-1 < index) {
078     result = numberStockList.get(index).intValue();
079   }
080   return result;
081 }
    /**
    * 在庫数クリア
    */
082 public void clearStock() {
083   clearStock(goodsName);
084 }
    /**
    * 在庫数クリア
    */
085 public void clearStock(String name) {
086   int index = checkGoods(name);
087   if (-1 < index) {
088     numberStockList.set(index, 0);
089   }
090 }
    /**
    * 在庫情報出力
    */
091 public void outputStock() {
092   StringBuilder sb = new StringBuilder();
093   for (int i = 0; i < goodsNameList.size(); i++) {
094     if (0 != i) {
095       sb.append("\n");
096     }
097     sb.append("商品名:");
098     sb.append(goodsNameList.get(i));
099     sb.append(" 在庫数:");
100     sb.append(numberStockList.get(i));
101     sb.append(" 説明:");
102     sb.append(descriptionList.get(i));
103   }
104   outputMessage(sb.toString());
105 }
    /**
    * グループ別在庫数出力
    */
106 public void outputStockByGroup() {
107   List<List<String>> groupStockList = UtilityObjectSample.getStockOfGroup(goodsNameList, 5, 6, numberStockList);
108   outputMessage("商品グループ名, 合計数, 平均数");
109   for (int i = 0; i <groupStockList.size(); i++) {
110     outputMessage(groupStockList.get(i).get(0) + ", " + groupStockList.get(i).get(1) + ", " + groupStockList.get(i).get(2) + ", ");
111   }
112 }
    /**
    * 追加商品チェック
    */
113 public int checkAdditionalGoods(String name) {
114   if (0 > checkGoodsName(name))  {
115     return -1;
116   }
117   if (goodsNameList.contains(name)) {
118     outputMessage("既に商品名は登録されています。");
119     return -1;
120   }
121   return 0;
122 }
    /**
    * 商品チェック
    */
123 public int checkGoods(String name) {
124   if (0 > checkGoodsName(name))  {
125     return -1;
126   }
127   int index = goodsNameList.indexOf(name);
128   if (0 > index) {
129     outputMessage(name + "は登録されていません。");
130     return -1;
131   }
132   return index;
133 }
    /**
    * 商品名チェック
    */
134 public int checkGoodsName(String name) {
135   if (null == goodsNameList) {
136     outputMessage("商品名が全く登録されていません。");
137     return -1;
138   }
139   if (null == name || "".equals(name)) {
140     outputMessage("商品名が指定されていません。");
141     return -1;
142   }
143   return 0;
144 }
    /**
    * メッセージ出力
    */
145 private void outputMessage(String mes) {
146   System.out.println(mes);
147 }
    /**
    * 説明を設定
    */
148 public void setDescription(String des) {
149   if (null == des) {
150     des = "";
151   }
152   descriptionList.add(des);
153 }
154 }
UtilityObjectSample.java
001 package functiontest.utility;
002 import java.util.ArrayList;
003 import java.util.Collections;
004 import java.util.List;
    /**
    * オブジェクト指向(命令型) 商品在庫管理用ユーティリティ
    */
005 public class UtilityObjectSample {
    /**
    * グループ別合計数及び平均値算出
    */
006 public static List<List<String>> getStockOfGroup(List<String> goodsData, int sgroup, int egroup, List<Integer> stockList) {
007   List<String> list = new ArrayList<String>();
008   for (String name : goodsData) {
009     String grp = name.substring(sgroup,  egroup);
010     if (!list.contains(grp)) {
011       list.add(grp);
012     }
013   }
014   Collections.sort(list);
015   List<List<String>> groupStockList = new ArrayList<List<String>>();
016   for (String gname : list) {
017     List<String> itemList =  new ArrayList<String>();
018     int count = 0;
019     float totalCount = 0;
020     for (int i = 0; i < goodsData.size(); i++) {
021       if (-1 < goodsData.get(i).indexOf(gname)) {
022         ++count;
023         totalCount = totalCount + stockList.get(i);
024       }
025     }
026     itemList.add(gname);
027     itemList.add(String.valueOf(totalCount));
028     itemList.add(String.valueOf(totalCount / count));
029     groupStockList.add(itemList);
030   }
031   return groupStockList;
032 }
033 }
StockFunctionSampleMain.java
001 package functiontest.stock;
    /**
    * 関数型プログラミング:商品在庫管理メインクラス
    * 	在庫情報:商品名、在庫数、商品説明の3要素で構成
    * 商品名:"Goods"+商品グループ名(1文字)+連番で構成
    */
002 public class StockFunctionSampleMain {
003   private static final String[] goodsNameA = {"GoodsA1", "GoodsA2", "GoodsA3"};
004   private static final String[] goodsNameB = {"GoodsB1", "GoodsB2", "GoodsB3"};
005   private static final int[] goodsStockA = {1, 2, 3};
006   private static final int[] goodsStockB = {1, 2, 3};
007   private static final String[] goodsDescriptionA = {"商品A1", "商品A2", "商品A3"};
008   private static final String[] goodsDescriptionB = {"商品B1", "商品B2", "商品B3"};
009 public static void main(String[] args) {
010   System.out.println("開始");
      //初期化
011   StockFunctionSample ss = initialize();
      //在庫追加
012   ss.executeFunction(goodsNameA[0], goodsStockA[0], ss.getICheckGoods(), ss.getIStock());
013   ss.executeFunction(goodsNameA[1], goodsStockA[1], ss.getICheckGoods(), ss.getIStock());
014   ss.executeFunction(goodsNameA[2], goodsStockA[2], ss.getICheckGoods(), ss.getIStock());
      //商品確認
015   System.out.println("商品確認(GoodsA1):" + ss.executeFunction(goodsNameA[0], -1, ss.getICheckGoods(), null));
      //在庫確認
016   System.out.println("在庫確認(GoodsA2):" + ss.executeFunction(goodsNameA[1], 2, ss.getICheckGoods(), ss.getICheckStock()));
      //在庫数確認
017   System.out.println("在庫数確認(GoodsA3):" + ss.getStock(goodsNameA[2], ss.getGoodsNameList()));
      //商品・在庫追加
018   StockFunctionSample ss1 = update(goodsNameB[0], goodsStockB[0], goodsDescriptionB[0]);
019   StockFunctionSample ss2 = update(goodsNameB[1], goodsStockB[1], goodsDescriptionB[1]);
020   StockFunctionSample ss3 = update(goodsNameB[2], goodsStockB[2], goodsDescriptionB[2]);
021   ss1.addGoods(null, 0,null);
022   ss2.addGoods(null, 0,null);
023   ss3.addGoods(null, 0,null);
024   update(goodsNameB[0], goodsStockB[0]).executeFunction(goodsNameB[0], goodsStockB[0], ss1.getICheckGoods(), ss1.getIStock());
025   update(goodsNameB[1], goodsStockB[1]).executeFunction(goodsNameB[1], goodsStockB[1], ss2.getICheckGoods(), ss2.getIStock());
026   update(goodsNameB[2], goodsStockB[2]).executeFunction(goodsNameB[2], goodsStockB[2], ss3.getICheckGoods(), ss3.getIStock());
      //在庫リスト出力
027   ss.outputStock(ss.getGoodsNameList(), ss.getNumberStockList(), ss.getDescriptionList());
      //グループ別在庫数出力
028   ss.outputStockByGroup(ss.getGoodsNameList(), ss.getNumberStockList());
029   System.out.println("終了");
030 }
    /**
    * 初期化
    */
031 private static StockFunctionSample initialize() {
032   StockFunctionSample ss = new StockFunctionSample();
033   ss.addGoods(goodsNameA[0], 0, goodsDescriptionA[0]);
034   ss.addGoods(goodsNameA[1], 0, goodsDescriptionA[1]);
035   ss.addGoods(goodsNameA[2], 0, goodsDescriptionA[2]);
      // Printer.printBasicObject("check01", ss.getGoodsNameList());
036   return ss;
037 }
    /**
    * 商品在庫情報の更新
    */
038 private static StockFunctionSample update(String name, int num, String des) {
039   StockFunctionSample ss = new StockFunctionSample(name, num, des);
040   return ss;
041 }
    /**
    * 商品在庫情報の更新
    */
042 private static StockFunctionSample update(String name, int num) {
043   StockFunctionSample ss = new StockFunctionSample(name, num);
044   return ss;
045 }
046 }
StockFunctionSample.java
001 package functiontest.stock;
002 import java.util.ArrayList;
003 import java.util.List;
004 import java.util.function.BiFunction;
005 import java.util.function.Consumer;
006 import java.util.function.Function;
007 import java.util.stream.Collectors;
008 import java.util.stream.IntStream;
009 import functiontest.utility.UtilityFunctionSample;
    /**
    * 関数型プログラミング:商品在庫管理クラス
    * 	在庫情報:商品名、在庫数、商品説明の3要素で構成
    * 商品名:"Goods"+商品グループ名(1文字)+連番で構成
    */
010 public class StockFunctionSample {
011   private static List<String> goodsNameList = new ArrayList<String>();
012   private static List<Integer> numberStockList = new ArrayList<Integer>();
013   private static List<String> descriptionList = new ArrayList<String>();
014   private GoodsInterface IAdditionalGoods;
015   private Function<String, Integer> ICheckGoods;
016   private BiFunction<Integer, Integer, Integer> IStock;
017   private BiFunction<Integer, Integer, Integer> IShip;
018   private BiFunction<Integer, Integer, Integer> ICheckStock;
019   private Function<Integer, Integer> IGetNumberStock;
020   private Consumer<Integer> IClearStock;
    /**
    * コンストラクタ
    */
021 public StockFunctionSample() {
022   implementsInterface(null, 0, null, goodsNameList,  numberStockList, descriptionList);
023 }
    /**
    * コンストラクタ
    */
024 public StockFunctionSample(String name, int num, String des) {
025   implementsInterface(name, num, des, goodsNameList,  numberStockList, descriptionList);
026 }
    /**
    * コンストラクタ
    */
027 public StockFunctionSample(String name, int num) {
028   implementsInterface(name, num, null, goodsNameList,  numberStockList, descriptionList);
029 }
    /**
    * 関数インターフェイスの実装
    */
030 private void implementsInterface(String goodsName, Integer numberStock, String description, List<String> goodsNameList,  List<Integer> numberStockList, List<String> descriptionList) {
031   implementsGoodsInterface(goodsName, numberStock, description, goodsNameList,  numberStockList, descriptionList);
032   implementsStockInterface(goodsNameList,  numberStockList, descriptionList);
033 }
    //商品登録用自作関数型インターフェース
034 @FunctionalInterface
035 private interface GoodsInterface {
036   public void addGoods(String name, int num, String des);
037 }
    //商品登録用インターフェースの実装
038 private void implementsGoodsInterface(String goodsName, Integer numberStock, String description, List<String> goodsNameList,  List<Integer> numberStockList, List<String> descriptionList) {
039   IAdditionalGoods = (x, y, z) -> {
040     if (null != x) {
041       if (-1 < checkAdditionalGoods(x, goodsNameList)) {
042         addGoodsData(x, y, z, goodsNameList,  numberStockList, descriptionList);
043       }
044     } else {
045       if (-1 < checkAdditionalGoods(goodsName, goodsNameList)) {
046         addGoodsData(goodsName, numberStock, description, goodsNameList,  numberStockList, descriptionList);
047       }
048     }
049   };
050 }
    //在庫数関連インターフェースの実装
051 private void implementsStockInterface(List<String> goodsNameList,  List<Integer> numberStockList, List<String> descriptionList) {
052   ICheckGoods = (x) -> { return checkGoods(x, goodsNameList);};
053   IStock = (x, y) -> {
054     int sval = numberStockList.get(x).intValue() + y;
055     numberStockList.set(x, sval);
056     return sval;
057   };
058   IShip = (x, y) -> {
059     int sval = numberStockList.get(x).intValue() - y;
060     numberStockList.set(x, sval);
061     return sval;
062   };
063   ICheckStock = (x, y) -> {
064     int result = 0;
065     if (y > numberStockList.get(x).intValue()) {
066       result = -1;
067     }
068     return result;
069   };
070   IGetNumberStock = (x) -> {
071     return numberStockList.get(x).intValue();
072   };
073   IClearStock = (x) -> {
074     numberStockList.set(x, 0);
075   };
076 }
    /**
    * 商品登録
    */
077 private void addGoodsData(String name, int num, String des, List<String> goodsNameList,  List<Integer> numberStockList, List<String> descriptionList) {
078   goodsNameList.add(name);
079   numberStockList.add(num);
080   setDescription(des, descriptionList);
081 }
    /**
    * 商品説明設定
    */
082 private void setDescription(String des, List<String> descriptionList) {
083   if (null == des) {
084     des = "";
085   }
086   descriptionList.add(des);
087 }
    /**
    * 商品登録
    */
088 public void addGoods(String name, int num, String des) {
089   IAdditionalGoods.addGoods(name, num, des);
090 }
    /**
    * 指定関数を実行するメソッド
    */
091 public int executeFunction(String name, Integer value, Function<String, Integer> ckfnc, BiFunction<Integer, Integer, Integer> dofnc) {
092   int result = -1;
093   if (null != ckfnc) {
094     if (null != name) {
095       result = ckfnc.apply(name);
096     }
097   }
098   if (-1 < result && null != dofnc) {
099     if (-1 < value) {
100       result = dofnc.apply(result, value);
101     }
102   }
103   return result;
104 }
    /**
    * 在庫数取得
    */
105 public int getStock(String name, List<String> goodsNameList) {
106   int result = 0;
107   int index = checkGoods(name, goodsNameList);
108   if (-1 < index) {
109     result = IGetNumberStock.apply(index);
110   }
111   return result;
112 }
    /**
    * 在庫数クリア
    */
113 public void clearStock(String name, List<String> goodsNameList) {
114   int index = checkGoods(name, goodsNameList);
115   if (-1 < index) {
116     IClearStock.accept(index);
117   }
118 }
    /**
    * 在庫情報出力
    */
119 public void outputStock(List<String> goodsNameList,  List<Integer> numberStockList, List<String> descriptionList) {
120   List<String> list = IntStream.range(0,  goodsNameList.size())
121   .mapToObj(i -> {
122     StringBuilder sbc = new StringBuilder();
123     sbc.append("商品名:");
124     sbc.append(goodsNameList.get(i));
125     sbc.append(" 在庫数:");
126     sbc.append(numberStockList.get(i));
127     sbc.append(" 説明:");
128     sbc.append(descriptionList.get(i));
129     return sbc.toString();
130     })
131     .collect(Collectors.toList());
132     outputMessage(list);
133   }
      /**
      * グループ別在庫数出力
      */
134 public void outputStockByGroup(List<String> goodsNameList,  List<Integer> numberStockList) {
135   List<List<String>> groupStockList = UtilityFunctionSample.getStockOfGroup(goodsNameList, 5, 6, numberStockList, 0, 0);
136   outputMessage("商品グループ名, 合計数, 平均数");
137   List<String> list = IntStream.range(0,  groupStockList.get(0).size())
138   .mapToObj(i -> {
139     StringBuilder sbc = new StringBuilder();
140     sbc.append(groupStockList.get(0).get(i));
141     sbc.append( ", ");
142     sbc.append(groupStockList.get(1).get(i));
143     sbc.append( ", ");
144     sbc.append(groupStockList.get(2).get(i));
145     sbc.append( ", ");
146     return sbc.toString();
147     })
148     .collect(Collectors.toList());
149     outputMessage(list);
150   }
      /**
      * 追加商品チェック
      */
151 public int checkAdditionalGoods(String name, List<String> goodsNameList) {
152   if (0 > checkGoodsName(name, goodsNameList))  {
153     return -1;
154   }
155   if (null != name && goodsNameList.contains(name)) {
156     outputMessage("既に商品名は登録されています。 " + name);
157     return -1;
158   }
159   return 0;
160 }
    /**
    * 商品チェック
    */
161 public int checkGoods(String name, List<String> goodsNameList) {
162   if (0 > checkGoodsName(name, goodsNameList))  {
163     return -1;
164   }
165   int index = goodsNameList.indexOf(name);
166   if (0 > index) {
167     outputMessage(name + "は登録されていません。");
168     return -1;
169   }
170   return index;
171 }
    /**
    * 商品名チェック
    */
172 public int checkGoodsName(String name, List<String> goodsNameList) {
173   if (null == goodsNameList) {
174     outputMessage("商品名が全く登録されていません。 " + name);
175     return -1;
176   }
177   if (null == name || "".equals(name)) {
178     outputMessage("商品名が指定されていません。 " + name);
179     return -1;
180   }
181   return 0;
182 }
    /**
    * メッセージ出力
    */
183 public void outputMessage(String mes) {
184   System.out.println(mes);
185 }
    /**
    * メッセージ出力
    */
186 private void outputMessage(List<String> list) {
187   list.stream().forEach(s -> {outputMessage(s);});
188 }
189 public Function<String, Integer> getICheckGoods() {
190   return ICheckGoods;
191 }
192 public BiFunction<Integer, Integer, Integer> getIStock() {
193   return IStock;
194 }
195 public BiFunction<Integer, Integer, Integer> getIShip() {
196   return IShip;
197 }
198 public BiFunction<Integer, Integer, Integer> getICheckStock() {
199   return ICheckStock;
200 }
201 public List<String> getGoodsNameList() {
202   return goodsNameList;
203 }
204 public List<Integer> getNumberStockList() {
205   return numberStockList;
206 }
207 public List<String> getDescriptionList() {
208   return descriptionList;
209 }
210 }
UtilityFunctionSample.java
001 package functiontest.utility;
002 import java.util.ArrayList;
003 import java.util.Collections;
004 import java.util.List;
005 import java.util.stream.Collectors;
006 import java.util.stream.Stream;
    /**
    * 関数型プログラミング 商品在庫管理用ユーティリティ
    */
007 public class UtilityFunctionSample {
    /**
    * グループ別合計数及び平均値算出
    */
008 public static List<List<String>> getStockOfGroup(List<String> goodsData, int sgroup, int egroup, List<Integer> stockList, int snumeric, int enumeric) {
      //グループ区分け及びソート
009   List<String> groupList = new ArrayList<String>();
010   goodsData.stream().filter(vf -> {if(!groupList.contains(vf.substring(sgroup, egroup))) {return true;}return false;}).forEach(ve -> {
011     groupList.add(ve.substring(sgroup, egroup));
012   });
013   Collections.sort(groupList);
      //グループ別合計数及び平均値算出
014   Stream<String> groupStock = groupList.stream().flatMap(gname -> {
        //gnameグループ名と一致する在庫数リストを作成
015     List<Integer> countList =  new ArrayList<Integer>();
016     goodsData.stream().filter(vf -> {if (-1 < vf.indexOf(gname)) {return true;}return false;}).forEach(ve -> {
017       if (null == stockList) {
018         countList.add(new Integer(ve.substring(snumeric, enumeric)));
019       } else {
020         countList.add(stockList.get(goodsData.indexOf(ve)));
021       }
022     });
        //グループ別合計数
023     float totalCount = countList.stream().reduce((i, t) -> i + t).get();
024     Stream<String> gelement = Stream.of("0"+gname);
025     gelement = Stream.concat(gelement, Stream.of("1"+String.valueOf(totalCount)));
        //グループ別平均値
026     gelement = Stream.concat(gelement, Stream.of("2"+String.valueOf(totalCount / countList.size())));
027     return gelement;
028   });
      //groupStock先頭文字  0:グループ名/1:合計数/2:平均値
029   List<String> glist = groupStock.collect(Collectors.toList());
030   List<String> nameList = createGroupList(glist.stream(), "0", 0, 1);
031   List<String> totList = createGroupList(glist.stream(), "1", 0, 1);
032   List<String> aveList = createGroupList(glist.stream(), "2", 0, 1);
033   List<List<String>> groupStockList = new ArrayList<List<String>>();
034   groupStockList.add(nameList);
035   groupStockList.add(totList);
036   groupStockList.add(aveList);
037   return groupStockList;
038 }
    /**
    * Stream<<String>から指定文字範囲が指定キーに一致するデータ(2文字目以降)をリスト化して戻す
    */
039 public static List<String> createGroupList(Stream<String> strm, String key, int sindx, int eindx) {
040   List<String> list = new ArrayList<String>();
041   strm.filter(s -> {if (key.equals(s.substring(sindx, eindx))) {return true;}return false;}).forEach(s -> {
042     list.add(s.substring(1));
043   });
044   return list;
045 }
046 }

現実的サンプルでの考察・推測

まずは、後述のまとめを含め現実的サンプルでの考察・推測をするための前提を以下2項目とします。
・オブジェクト指向ベースのシステム開発で、純粋な関数型プログラミングの知識が少ないメンバーがほとんどである。
・StreamAPIの利用に関する知識は保有している。
Javaで関数型プログラミングを取り入れる目的としては、
(1)副作用を少なくし品質をあげる。
(2)コーディング量を少なくする。
が一般的に挙げられています。
(1)については確かに表層的・部分的には副作用を減少させることが可能ではありますが、上述で述べているとおり本質的な対応は困難ですし、無理矢理適用すればメソッド引数がかなり多くなりプログラミング上好ましくない状態となり、データと処理操作が乖離していきベースとなるオブジェクト指向から遠ざかっていくこととなります。
今回の現実的サンプルでもメソッドの引数はかなり増加していてプログラムの見通しが悪くなり無駄も発生しています。
(2)のコーディング量については、公開サイトでよく見られる数行のサンプルでは少なくなるように思えますが、一般的にはforループ内は少し複雑な処理となったり、インデックス使用となることが比較的多いので余計な対応が必要となったりして結局それ程の効果はないと推測され、今回の現実的サンプルでも少なくはなっていない。
また、streamAPIを多用すればstream化や逆stream化処理で処理時間が遅くなることも想定されます。

まとめ

現実的サンプルでの考察・推測をふまえて、最終的にJavaでは関数型プログラミングをどのように扱えばよいのかをまとめたいと思います。
あくまで個人的な意見・対応方針となりますが、
(a)関数型プログラミング主体でシステム開発したい場合はHaskell等の純粋関数型言語を使用しJavaでは行わない。
(b)システム開発設計はきちんとオブジェクト指向で行う。
(c)あえて副作用をなくすため(参照透過性保証)にオブジェクト指向に反する設計やプログラミングは行わない。
(d)無理して関数型プログラミングに関係した手法(自作高階関数、カリー化など)を取り込まない。
(e)Javaで標準として用意されている関数プログラミング用APIをフィットする範囲で利用する範囲にとどめてプログラミングを行う。
(f)コレクションfor文をなくすために毎回あえてstreamAPIを使用することはしない。
(g)for文内が単純でstreamAPIにフィットする処理ケース以外はfor文をそのまま使用した方が見通しも良く、処理速度劣化も無い(並列ストリーム使用時を除く)。
(h)静的ユーティリティ用メソッドはオブジェクトのメソッドよりも多少ではあるが副作用排除はしやすいのではと思われる。
以上のまとめをざっくり言えば、Javaを用いてオブジェクト指向で開発する範囲においては、表層的・部分的な副作用回避のために関数型プログラミングを多用する意義はそれほど大きくはなく、コーディング量の減少効果も全体的にみればそれほど期待はできないと思われる。
ただ、この関数型プログラミング効果は開発対象や開発メンバーに左右されるので、よく調査しベンチマークの上どの程度取り入れるべきかを決定することが妥当である事はいうまでもない。
という内容が今回の活動での結論・総括となりました。

最後に

以上です。最後までお読み頂き有難うございました。

3
2
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
3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?