「関数型プログラミングと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における関数型プログラミングスタイルでは一般的によく使用されます。
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情報格納クラス
*/
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文字列結合を実行しています。
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つの命令型比較メソッド。
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メソッド使用)を用いて算出しています。
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 }
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 }
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 }
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 }
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 }
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を用いてオブジェクト指向で開発する範囲においては、表層的・部分的な副作用回避のために関数型プログラミングを多用する意義はそれほど大きくはなく、コーディング量の減少効果も全体的にみればそれほど期待はできないと思われる。
ただ、この関数型プログラミング効果は開発対象や開発メンバーに左右されるので、よく調査しベンチマークの上どの程度取り入れるべきかを決定することが妥当である事はいうまでもない。
という内容が今回の活動での結論・総括となりました。
最後に
以上です。最後までお読み頂き有難うございました。