LoginSignup
0

More than 1 year has passed since last update.

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

Last updated at Posted at 2022-07-14

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

「実際Javaシステム開発においてどの程度関数型プログラミングを取り込むべきなのか?」を中心テーマとした第2部はすでに投稿されています。
 第2部へのリンク

はじめに

第1部では関数型インターフェイス、Map、List、Streamにおける関数プログラミングのサンプルを紹介しましたが、第3部ではこの中でも把握しずらいStreamにおけるmap、flatMapとcollectメソッドを中心にしたサンプルを紹介いたします。
合わせて関数プログラミングに関連したStringメソッドや知っておくと便利なArraysとCollections、それにmapやflatMapでよく使用するCollectorsについてもサンプル紹介いたします。
なお内容には主観にもとづく部分や、個人的指向が入ったプログラムスタイルを用いている場合もありますので了承お願い致します。
また記述内容はあくまで閲覧者自身がプログラミングする際の参考情報という位置づけなので、このようにプログラミングする必要があるというものではありません。
またどのクラスケースでも全てのメソッドを紹介しているわけでななく、また引数も他パターンがある場合もありますので実際にプログラミングする際にはJavaAPI技術資料等で確認して下さい。

Stringメソッドとサンプル

Stringクラスにも関数プログラミングに関連するメソッドとしてcharsとcodePointsがあります。
両メソッドとも文字列からintStreamを生成して戻します。
なおcharAtメソッドは文字のchar値を確認するための参考メソッドとして紹介しています。
executeメソッドは各メソッドを実行するための呼び出しメソッドです。

メソッド名 説明     
charAt  文字列における指定インデックスに位置するchar値(文字)を返します。
chars  文字列のchar値(0拡張int値)をintStreamで返します。
codePoints  文字列のコードポイント(Unicode)をintStreamで返します。
/**
 * Stringクラスメソッドのサンプル
 */
public class StringSample {
	//a:U+0061(0x0061), a:, あ:U+3042(0x3042), ??:U+2A5F1(0x2A5F1)[0xD869,0xDDF1]
	//??はサロゲートペア文字
	private static String value = "abaあい??xyz";
	private static int count;
	private static int surpos;

	public static void execute() {
    	System.out.println("文字列:" + value);
    	System.out.println("文字数:" + value.codePointCount(0, value.length()));
    	System.out.println("char数:" + value.length());
		printLine();
		charAt();
		printLine();
		chars();
		printLine();
		codePoints();
		printLine();
	}

    /**
     * 文字列における指定インデックスに位置するchar値(文字)を返します。
     * インデックスは0以上の値。
     * nullの場合:java.lang.NullPointerException
     * インデックスが文字列長より大きい場合:java.lang.StringIndexOutOfBoundsException
     * 出力結果:
     * 文字/char値(整数:10進数)/(16進数)
     * charAt0:a/97/61
     * charAt4:い/12356/3044
     * charAt5:?/55401/d869
     */
    public static void charAt() {
    	System.out.println("文字/char値(整数:10進数)/(16進数)");
    	char ch = value.charAt(0);
    	System.out.println("charAt0"+ ":" + ch + "/" + (int)ch + "/" + intToHex((int)ch));
    	ch = value.charAt(4);
    	System.out.println("charAt4"+ ":" + ch + "/" + (int)ch + "/" + intToHex((int)ch));
    	ch = value.charAt(5);
    	System.out.println("charAt5"+ ":" + ch + "/" + (int)ch + "/" + intToHex((int)ch));
    }

    /**
     * 文字列のchar値(0拡張int値)をintStreamで返します。
     * nullの場合:java.lang.NullPointerException
     * サンプル:
     *  ・文字列におけるchar値を出力。サロゲートペア文字である場合はその位置を格納。
     *  ・文字列におけるchar値を順にチェックしa(97)orあ(12354)の場合には前部に"chars_process_"を付加して出力する。
     * 出力結果:
     * chars長さ:10
     * 文字/char値(整数:10進数)/(整数:16進数:UTF-16)
     * chars1:/a/97/61
     * chars2:/b/98/62
     * chars3:/a/65345/ff41
     * chars4:/あ/12354/3042
     * chars5:/い/12356/3044
     * chars6:/?/55401/d869
     * chars7:/?/56817/ddf1
     * chars8:/x/120/78
     * chars9:/y/121/79
     * chars10:/z/122/7a
     * chars_process_97
     * chars_process_12354
     * サロゲートペア文字位置:6
     */
    public static void chars() {
    	count = 1;
    	surpos = value.length();
    	System.out.println("chars長さ:" + value.chars().count());
    	System.out.println("文字/char値(整数:10進数)/(整数:16進数:UTF-16)");
		value.chars().forEach((v -> {if (Character.isHighSurrogate((char)v)){surpos = count;}
			System.out.println("chars"+ count + ":" + "/" + value.charAt(count-1) + "/" + v + "/" + intToHex(v));++count;}));
		value.chars().filter(v -> {return (v == 97 || v == 12354);}).forEach((v -> {System.out.println("chars_process_"+v);}));
    	if (value.length() != surpos) System.out.println("サロゲートペア文字位置:" + surpos);
    }

    /**
     * 文字列のコードポイント(Unicode)をintStreamで返します。
     * nullの場合:java.lang.NullPointerException
     * サンプル:
     *  ・文字列におけるコードポイントを出力。サロゲートペア文字位置以降は出力文字インデックスに1を加算。
     *  ・文字列におけるコードポイントを順にチェックしa(97)orあ(12354)の場合には前部に"codePoints_process_"を付加して出力する。
     * 出力結果:
     * codePoints長さ:9
     * 文字/Unicodeコードポイント(10進数)/(16進数)
     * codepoints1:a/97/61
     * codepoints2:b/98/62
     * codepoints3:a/65345/ff41
     * codepoints4:あ/12354/3042
     * codepoints5:い/12356/3044
     * codepoints6:?/173553/2a5f1
     * codepoints7:x/120/78
     * codepoints8:y/121/79
     * codepoints9:z/122/7a
     * codePoints_process_97
     * codePoints_process_12354
     */
    public static void codePoints() {
		count = 1;
    	System.out.println("codePoints長さ:" + value.codePoints().count());
    	System.out.println("文字/Unicodeコードポイント(10進数)/(16進数)");
		value.codePoints().forEach((v -> {int index = count;if (surpos < count) {++index;}System.out.println("codepoints"+ count + ":" + value.charAt(index-1) + "/" + v + "/" +  intToHex(v));++count;}));
		value.codePoints().filter(v -> {return (v == 97 || v == 12354);}).forEach((v -> {System.out.println("codePoints_process_"+v);}));
    }

    /**
     * 10進整数→16進整数に変換
     */
    public static String intToHex(int intval) {
    	return Integer.toHexString(intval);
    }

    /**
     * 16進整数→10進整数に変換
     */
    public static int hexToInt(String hex) {
    	return  Integer.parseInt(hex, 16);
    }

    public static void printLine() {
    	System.out.println("------------------------------");
    }

    /**
     * Unicodeを取得
     */
    public static String getUnicode(int chint) {
    	return String.format("\\u%04X", chint);
    }

    /**
     * Unicodeを取得
     */
    public static String getUnicode(char ch) {
    	return String.format("\\u%04X", (int)ch);
    }

Collectionsメソッドとサンプル

CollectionsはListやSet等のコレクションを扱うための便利機能ユーティリティクラスです。
関数プログラミング時だけでなく普通のJavaプログラミング時にもよく使うので知っていれば便利なメソッドです。
executeメソッドは各メソッドを実行するための呼び出しメソッドです。

メソッド名 説明     
copy  List(第2引数)からList(第1引数)へのコピー。
sort  List(第1引数)のソート(昇順、降順など)を実施。
binarySearch  List(第1引数)から指定したオブジェクト(第2引数)を検索し結果をインデックスで戻す。
fill  List(第1引数)内の全要素を指定したオブジェクト(第2引数)に置換える。
indexOfSubList  List(第1引数)における指定したList(第2引数)が最初に出現するインデックスを戻す。
max  コレクション(第1引数)における最大値を戻す。
min  コレクション(第1引数)における最小値を戻す。
reverse  List(第1引数)の要素を逆順にします。
rotate  List(第1引数)の要素を指定要素数分(負値可)ローテーションします。
swap  List(第1引数)における指定要素(第2引数、第3引数)を入れ替える。
replaceAll  List(第1引数)内要素において指定したオブジェクト(第2引数)を全て指定したオブジェクト(第3引数)に置き換える。
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

/**
 * Collectionsクラスメソッドのサンプル
 */
public class CollectionsSample {
	private static List<String> alplist = Arrays.asList("a", "b", "c", "d", "e");
	private static List<Integer> intlist = Arrays.asList(new Integer[] {1, 2, 3, 4, 5});
	//
	private static List<String> cpyalplist = Arrays.asList("", "", "", "", "");
	private static List<Integer> cpyintlist = Arrays.asList(new Integer[] {0, 0, 0, 0, 0});

	public static void execute() {
		copy();
		sort();
		copy();
		binarySearch();
		fill();
		copy();
		indexOfSubList();
		max();
		min();
		reverse();
		copy();
		rotate();
		copy();
		swap();
		copy();
		replaceAll();
		copy();
	}

	/**
	 * List(第2引数)からList(第1引数)へのコピー。
	 */
	public static void copy() {
		Collections.copy(cpyalplist, alplist);
		Collections.copy(cpyintlist, intlist);
	}

	/**
	 * List(第1引数)のソート(昇順、降順など)を実施。
	 * 出力結果:
	 * sort-result1:[a, b, c, d, e]
	 * sort-result2:[5, 4, 3, 2, 1]
	 */
	public static void sort() {
		Collections.sort(cpyalplist);
		Collections.sort(cpyintlist, Comparator.reverseOrder()); //降順
		System.out.println("sort-result1:" + cpyalplist);
		System.out.println("sort-result2:" + cpyintlist);
	}

	/**
	 * List(第1引数)から指定したオブジェクト(第2引数)を検索し結果をインデックスで戻す。
	 * バイナリ・サーチ・アルゴリズムを使用。
	 * 無い場合は負の値が戻る。
	 * 出力結果:
	 * binarySearch-result1:0
	 * binarySearch-result2:2
	 */
	public static void binarySearch() {
		int index1 = Collections.binarySearch(intlist, 1);
		int index2 = Collections.binarySearch(alplist, "c");
		System.out.println("binarySearch-result1:" + index1);
		System.out.println("binarySearch-result2:" + index2);
	}

	/**
	 * List(第1引数)内の全要素を指定したオブジェクト(第2引数)に置換える。
	 * 出力結果:
	 * fill-result1:[-1, -1, -1, -1, -1]
	 * fill-result2:[a, a, a, a, a]
	 */
	public static void fill() {
		Collections.fill(cpyintlist, -1);
		System.out.println("fill-result1:" + cpyintlist);
		Collections.fill(cpyalplist, "a");
		System.out.println("fill-result2:" + cpyalplist);
	}

	/**
	 * List(第1引数)における指定したList(第2引数)が最初に出現するインデックスを戻す。
	 * 無い場合は-1が戻る。
	 * 出力結果:
	 * indexOfSubList-result1:1
	 * indexOfSubList-result2:2
	 */
	public static void indexOfSubList() {
		int rlt1 = Collections.indexOfSubList(alplist, Arrays.asList("b", "c"));
		int rlt2 = Collections.indexOfSubList(intlist, Arrays.asList(3, 4, 5));
		System.out.println("indexOfSubList-result1:" + rlt1);
		System.out.println("indexOfSubList-result2:" + rlt2);
	}

	/**
	 * コレクション(第1引数)における最大値を戻す。
	 * 出力結果:
	 * max-result1:e
	 * max-result2:5
	 */
	public static void max() {
		String rlt1 = Collections.max(alplist);
		int rlt2 = Collections.max(intlist);
		System.out.println("max-result1:" + rlt1);
		System.out.println("max-result2:" + rlt2);
	}

	/**
	 * コレクション(第1引数)における最小値を戻す。
	 * 出力結果:
	 * min-result1:a
	 * min-result2:1
	 */
	public static void min() {
		String rlt1 = Collections.min(alplist);
		int rlt2 = Collections.min(intlist);
		System.out.println("min-result1:" + rlt1);
		System.out.println("min-result2:" + rlt2);
	}

	/**
	 * List(第1引数)の要素を逆順にします。
	 * 出力結果:
	 * reverse-result1:[e, d, c, b, a]
	 * reverse-result2:[5, 4, 3, 2, 1]
	 */
	public static void reverse() {
		Collections.reverse(cpyalplist);
		Collections.reverse(cpyintlist);
		System.out.println("reverse-result1:" + cpyalplist);
		System.out.println("reverse-result2:" + cpyintlist);
	}

	/**
	 * List(第1引数)の要素を指定要素数分(負値可)ローテーションします。
	 * ローテーションにおいて最後尾要素を後ろに移動させた場合は先頭要素に、
	 * 先頭要素を前にずらした場合は最後尾要素になる。
	 * 出力結果:
	 * rotate-result1:[e, a, b, c, d]
	 * rotate-result2:[4, 5, 1, 2, 3]
	 */
	public static void rotate() {
		Collections.rotate(cpyalplist, 1);
		Collections.rotate(cpyintlist, 2);
		System.out.println("rotate-result1:" + cpyalplist);
		System.out.println("rotate-result2:" + cpyintlist);
	}

	/**
	 * List(第1引数)における指定要素(第2引数、第3引数)を入れ替える。
	 * 出力結果:
	 * swap-result1:[a, c, b, d, e]
	 * swap-result2:[1, 2, 4, 3, 5]
	 */
	public static void swap() {
		Collections.swap(cpyalplist, 1, 2);
		Collections.swap(cpyintlist, 2, 3);
		System.out.println("swap-result1:" + cpyalplist);
		System.out.println("swap-result2:" + cpyintlist);
	}

	/**
	 * List(第1引数)内要素において指定したオブジェクト(第2引数)を全て指定したオブジェクト(第3引数)に置き換える。
	 * 実際に置換えが発生した場合はtrueが戻る。発生しなかった場合はfalseが戻る。
	 * 出力結果:
	 * replaceAll-result1:true/[a, b, z, d, e]
	 * replaceAll-result2:true/[1, 9, 3, 4, 5]
	 */
	public static void replaceAll() {
		boolean rlt1 = Collections.replaceAll(cpyalplist, "c", "z");
		boolean rlt2 = Collections.replaceAll(cpyintlist, 2, 9);
		System.out.println("replaceAll-result1:" + rlt1 + "/" + cpyalplist);
		System.out.println("replaceAll-result2:" + rlt2 + "/" +  cpyintlist);
	}
}

Arraysメソッドとサンプル

Arraysは配列を扱うための便利機能ユーティリティクラスです。
Collectionsと同様に関数プログラミング時だけでなく普通のJavaプログラミング時にもよく使うので知っていれば便利なメソッドです。

メソッド名 説明     
copyOf  配列(第1引数)から指定要素分(第2引数)コピーして戻す。
asList  配列(第1引数)からListを生成する。
sort  配列(第1引数)のソート(昇順、降順など)を実施。
binarySearch  配列(第1引数)から指定したオブジェクト(第2引数)を検索し結果をインデックスで戻す。
compare  配列(第1引数)と指定したオブジェクト(第2引数)を比較(辞書順)し結果を整数値で戻す。
fill  配列(第1引数)内の全要素を指定したオブジェクト(第2引数)に置換える。
equals  配列(第1引数と第2引数)内の全要素が同じ(順序と値)か比較し結果を論理値で戻す。
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;

/**
 * Arraysクラスメソッドのサンプル
 */
public class ArraysSample {
	private static String[] alparray = {"a", "b", "c", "d", "e"};
	private static String[] dalparray = {"a", "b", "a", "d", "e"};
	private static int[] intarray = new int[] {1, 2, 3, 4, 5};
	private static Integer[] integerarray = new Integer[] {1, 2, 3, 4, 5};
	//
	private static String[] cpyalparray = new String[alparray.length];
	private static int[] cpyintarray = new int[intarray.length];
	private static Integer[] cpyintegerarray = new Integer[integerarray.length];

	public static void execute() {
		copyOf();
		asList();
		sort();
		copyOf();
		binarySearch();
		compare();
		fill();
		copyOf();
		equals();
	}

	/**
	 * 配列(第1引数)から指定要素分(第2引数)コピーして戻す。
	 */
	public static void copyOf() {
		cpyalparray = Arrays.copyOf(alparray, alparray.length);
		cpyintarray = Arrays.copyOf(intarray, intarray.length);
		cpyintegerarray = Arrays.copyOf(integerarray, integerarray.length);
	}

	/**
	 * 配列(第1引数)からListを生成する。
	  * 出力結果:
	  * asList-result1:[a, b, c, d, e]
	  * asList-result2:[1, 2, 3, 4, 5]
	 */
	public static void asList() {
		List<String> rlt1 = Arrays.asList(alparray);
		List<Integer> rlt2 = Arrays.asList(integerarray);
		System.out.println("asList-result1:" + rlt1);
		System.out.println("asList-result2:" + rlt2);
	}

	/**
	 * 配列(第1引数)のソート(昇順、降順など)を実施。
	 * 出力結果:
	 * sort-result1:[a, b, c, d, e]
	 * sort-result2:[1, 2, 3, 4, 5]
	 * sort-result3:[5, 4, 3, 2, 1]
	 */
	public static void sort() {
		Arrays.sort(cpyalparray);
		System.out.println("sort-result1:" + Arrays.toString(cpyalparray));
		Arrays.sort(cpyintarray);
		System.out.println("sort-result2:" + Arrays.toString(cpyintarray));
		Arrays.sort(cpyintegerarray, Comparator.reverseOrder());
		System.out.println("sort-result3:" + Arrays.toString(cpyintegerarray));
	}

	/**
	 * 配列(第1引数)から指定したオブジェクト(第2引数)を検索し結果をインデックスで戻す。
	 * バイナリ・サーチ・アルゴリズムを使用。
	 * 無い場合は負の値が戻る。
	 * 出力結果:
	 * binarySearch-result1:0
	 * binarySearch-result2:1
	 * binarySearch-result3:2
	 */
	public static void binarySearch() {
		int rlt1 = Arrays.binarySearch(intarray, 1);
		int rlt2 = Arrays.binarySearch(integerarray, 2);
		int rlt3 = Arrays.binarySearch(alparray, "c");
		System.out.println("binarySearch-result1:" + rlt1);
		System.out.println("binarySearch-result2:" + rlt2);
		System.out.println("binarySearch-result3:" + rlt3);
	}

	/**
	 * 配列(第1引数)と指定したオブジェクト(第2引数)を比較(辞書順)し結果を整数値で戻す。
	 * 無い場合は負の値が戻る。
	 * 配列(第1引数と第2引数)を辞書順で比較する。
	 * 両配列が等しければ0が戻る。
	 * 第1引数が第2引数よりも辞書順先行であれば負の値を戻す。
	 * 第2引数の方が先行ならば正の値を戻す。
	 * 出力結果:
	 * compare-result1:0
	 * compare-result2:0
	 * compare-result3:2
	 */
	public static void compare() {
		int rlt1 = Arrays.compare(intarray, cpyintarray);
		int rlt2 = Arrays.compare(integerarray, cpyintegerarray);
		int rlt3 = Arrays.compare(alparray, dalparray);
		System.out.println("compare-result1:" + rlt1);
		System.out.println("compare-result2:" + rlt2);
		System.out.println("compare-result3:" + rlt3);
	}

	/**
	 * 配列(第1引数)内の全要素を指定したオブジェクト(第2引数)に置換える。
	 * 出力結果:
	 * fill-result1:[0, 0, 0, 0, 0]
	 * fill-result2:[1, 1, 1, 1, 1]
	 * fill-result3:[a, a, a, a, a]
	 */
	public static void fill() {
		Arrays.fill(cpyintarray, 0);
		System.out.println("fill-result1:" + Arrays.toString(cpyintarray));
		Arrays.fill(cpyintegerarray, 1);
		System.out.println("fill-result2:" + Arrays.toString(cpyintegerarray));
		Arrays.fill(cpyalparray, "a");
		System.out.println("fill-result3:" + Arrays.toString(cpyalparray));
	}

	/**
	 * 配列(第1引数と第2引数)内の全要素が同じ(順序と値)か比較し結果を論理値で戻す。
	 * 出力結果:
	 * equals-result1:true
	 * equals-result2:true
	 * equals-result3:false
	 */
	public static void equals() {
		boolean rlt1 = Arrays.equals(intarray, cpyintarray);
		boolean rlt2 = Arrays.equals(integerarray, cpyintegerarray);
		boolean rlt3 = Arrays.equals(alparray, dalparray);
		System.out.println("equals-result1:" + rlt1);
		System.out.println("equals-result2:" + rlt2);
		System.out.println("equals-result3:" + rlt3);
	}
}

Streamクラスcollectメソッドのサンプル

collectメソッドはstream要素を使用して基本的な処理(結合、グループ化、合計等)を行うためのメソッドです。
引数が1つのケースと3つのケースがあり、1つの場合は引数はCollector型となり一般的にCollectorsメソッドを用いることになります。
3つのケースは、stream要素を格納するオブジェクトを設定する関数、格納処理する関数、要素統合する関数という3つの引数(関数)を指定します。
3つのケースはCollectorsメソッドでカバーできない処理をする場合に向いています。
以下のサンプルではconnectStringByListとconnectStringByStringBuilderが3つにケースになります。
なお、1つのケースでのサンプルメソッド名はCollectorsメソッド名と同じにしています。

メソッド名 説明     
joining  入力リスト値(文字列)をコンマで区切り<>で囲む。
groupingBy  入力リスト値(文字列)の文字数でグループ分け(し入力文字列を結合)する。
mapping  入力リスト値(数値)を文字列に変換してこれらをコンマで結合し文字列を作成する。
flatMapping  入力リスト値(数値インデックス)から2つの文字列リスト値(キーと値)を取得しこれを使用してMapを作成する。
filtering  入力リスト値(文字列)から指定文字(a)を含む文字列を抽出しこれをList化する。
counting  入力リストの数を取得する。
minBy  入力リスト値の最小値を取得する。
maxBy  入力リスト値の最大値を取得する。
summingInt  入力リスト値の合計値(あるいは文字数合計値)を取得する。
averagingInt  入力リスト値の平均値(あるいは文字数平均値)を取得する。
reducing  入力リスト値を集約(結合)する。
partitioningBy  入力リスト値を指定条件(aを含むか)を満たすか否かで2グループに分けList化(Set化)しMapで戻す。
toMap  入力リスト値からMap(キー:入力リスト値、値:入力文字インデックスに位置する数値文字列)を作成する。
summarizingInt  入力リスト値から各種集計値(数、合計、最小値、平均値、最大値)を算出する。
connectStringByList  入力リスト値(数値文字列)をインデックスとして他リストの文字列を取得して文字列配列Listを一旦作成し、そのList要素の2つの配列文字列を結合して再度Listを作成する。
connectStringByStringBuilder  入力リスト値(文字列)をStringBuilderで結合する。
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.IntSummaryStatistics;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * Streamクラスcollectメソッドのサンプル
 */
public class CollectSample {
	private static List<String> alplist = Arrays.asList("a", "b", "c", "d", "e");
	private static List<String> dalplist = Arrays.asList("a", "b", "aa", "d", "e");
	private static List<String> falplist = Arrays.asList("f", "g", "h", "i", "j");
	private static List<String> talplist = Arrays.asList("a", "b", "a", "d", "e");
	private static List<String> numlist = Arrays.asList("1", "2", "3", "4", "5");
	private static List<String> totalplist = Arrays.asList("a", "b", "c", "d", "e", "f", "g", "h", "i", "j");
	private static List<String> totnumlist = Arrays.asList("1", "2", "3", "4", "5", "6", "7", "8", "9", "10");
	private static List<Integer> intlist = Arrays.asList(new Integer[] {1, 2, 3, 4, 5});

	public static void execute() {
		//joining();
		//groupingBy();
		//mapping();
		//flatMapping();
		//filtering();
		//counting();
		//minBy();
		//maxBy();
		//summingInt();
		//averagingInt();
		//reducing();
		//partitioningBy();
		//toMap();
		//summarizingInt();
		//connectStringByList();
		connectStringByStringBuilder();
	}

	/**
	 * 入力リスト値(文字列)をコンマで区切り<>で囲む。
	 * Stream要素を結合する。
	 * 出力結果:
	 * joining-result:<a,b,aa,d,e>
	 */
	public static void joining() {
		String rlt = dalplist.stream().collect(Collectors.joining(",", "<", ">"));
		System.out.println("joining-result:" + rlt);
	}

	/**
	 * 入力リスト値(文字列)の文字数でグループ分け(し入力文字列を結合)する。
	 * Stream要素をグループ分けする。
	 * 出力結果:
	 * groupingBy-result1:{1=[a, b, d, e], 2=[aa]}
	 * groupingBy-result2:{1=abde, 2=aa}
	 */
	public static void groupingBy() {
		Map<Integer, List<String>> rlt1 = dalplist.stream().collect(Collectors.groupingBy(String::length));
		System.out.println("groupingBy-result1:" + rlt1);
		Map<Integer, String> rlt2 = dalplist.stream().collect(Collectors.groupingBy(t -> t.length(), Collectors.joining()));
		System.out.println("groupingBy-result2:" + rlt2);
	}

	/**
	 * 入力リスト値(数値)を文字列に変換してこれらをコンマで結合し文字列を作成する。
	 * Stream要素を型変換して他のCollectorで処理する。
	 * 出力結果:
	 * mapping-result:1,2,3,4,5
	 */
	public static void mapping() {
		String rlt = intlist.stream().collect(Collectors.mapping(i -> String.valueOf(i), Collectors.joining(",")));
		System.out.println("mapping-result:" + rlt);
	}

	/**
	 * 入力リスト値(数値インデックス)から2つの文字列リスト値(キーと値)を取得しこれを使用してMapを作成する。
	 * Stream要素を型変換を伴う処理(倍要素数)後再度Streamに変換して他のCollector(toMap)でMap化処理する。
	 * 出力結果:
	 * flatMapping-result:
	 * size=10
	 * {a=1, b=2, c=3, d=4, e=5, f=6, g=7, h=8, i=9, j=10}
	 */
	public static void flatMapping() {
		List<String> list = new ArrayList<String>();
		Map<String, String> rltMap = intlist.stream().collect(Collectors.flatMapping(i -> {
			list.clear();list.add(alplist.get(i-1));list.add(falplist.get(i-1));return list.stream();},
			Collectors.toMap(t -> t, t -> {return totnumlist.get(totalplist.indexOf(t));})));
		System.out.println("flatMapping-result:" + "\n size=" + rltMap.size() + "\n " + rltMap);
	}

	/**
	 * 入力リスト値(文字列)から指定文字(a)を含む文字列を抽出しこれをList化する。
	 * Stream要素から条件を満たす要素を抽出し他のCollector(toList)でList化処理する。
	 * 出力結果:
	 * filtering-result:[a, aa]
	 */
	public static void filtering() {
		List<String> rlt = dalplist.stream().collect(Collectors.filtering(t -> t.contains("a"), Collectors.toList()));
		System.out.println("filtering-result:" + rlt);
	}

	/**
	 * 入力リストの数を取得する。
	 * Stream要素の数を取得する。
	 * 出力結果:
	 * counting-result:5
	 */
	public static void counting() {
		long rlt = dalplist.stream().collect(Collectors.counting());
		System.out.println("counting-result:" + rlt);
	}

	/**
	 * 入力リスト値の最小値を取得する。
	 * Stream要素の最小値を取得する。
	 * 出力結果:
	 * minBy-result1:1
	 * minBy-result2:a
	 */
	public static void minBy() {
		Integer rlt1 = intlist.stream().collect(Collectors.minBy(Comparator.naturalOrder())).get();
		String rlt2 = alplist.stream().collect(Collectors.minBy(Comparator.naturalOrder())).get();
		System.out.println("minBy-result1:" + rlt1);
		System.out.println("minBy-result2:" + rlt2);
	}

	/**
	 * 入力リスト値の最大値を取得する。
	 * Stream要素の最大値を取得する。
	 * 出力結果:
	 * maxBy-result1:5
	 * maxBy-result2:e
	 */
	public static void maxBy() {
		Integer rlt1 = intlist.stream().collect(Collectors.maxBy(Comparator.naturalOrder())).get();
		String rlt2 = alplist.stream().collect(Collectors.maxBy(Comparator.naturalOrder())).get();
		System.out.println("maxBy-result1:" + rlt1);
		System.out.println("maxBy-result2:" + rlt2);
	}

	/**
	 * 入力リスト値の合計値(あるいは文字数合計値)を取得する。
	 * Stream要素の合計値(あるいは文字数合計値)を取得する。
	 * 出力結果:
	 * summingInt-result:15/15/6
	 */
	public static void summingInt() {
		int rlt1 = numlist.stream().collect(Collectors.summingInt((t -> Integer.parseInt(t))));
		int rlt2 = intlist.stream().collect(Collectors.summingInt((t -> t)));
		int rlt3 = dalplist.stream().collect(Collectors.summingInt((t -> t.length())));
		System.out.println("summingInt-result:" + rlt1 + "/" + rlt2 + "/" + rlt3);
	}

	/**
	 * 入力リスト値の平均値(あるいは文字数平均値)を取得する。
	 * Stream要素の平均値(あるいは文字数平均値)を取得する。
	 * 出力結果:
	 * averageInt-result:3.0/3.0/1.2
	 */
	public static void averagingInt() {
		Double rlt1 = numlist.stream().collect(Collectors.averagingInt((Integer::parseInt)));
		Double rlt2 = intlist.stream().collect(Collectors.averagingInt(Integer::intValue));
		Double rlt3 = dalplist.stream().collect(Collectors.averagingInt(t -> t.length()));
		System.out.println("averageInt-result:" + rlt1 + "/" + rlt2 + "/" + rlt3);
	}

	/**
	 * 入力リスト値を集約(結合)する。
	 * Stream要素に指定した演算(BiaryOperator)を行い集約(結合)する。
	 * 出力結果:
	 * reducing-result:reducing-abaade/ab:aa:d:e:
	 */
	public static void reducing() {
		String rlt1 = dalplist.stream().collect(Collectors.reducing("reducing-", ((i, t) ->i + t)));
		String rlt2 = dalplist.stream().collect(Collectors.reducing((i, t) ->i + t + ":")).get();
		System.out.println("reducing-result:" + rlt1 + "/" + rlt2);
	}

	/**
	 * 入力リスト値を指定条件(aを含むか)を満たすか否かで2グループに分けList化(Set化)しMapで戻す。
	 * Stream要素に指定した条件演算を行い2グループに分けList化(Set化:他Collector使用)しMapで戻す。
	 * 出力結果:
	 * partitioningBy-result1:{false=[b, d, e], true=[a, aa]}
	 * partitioningBy-result2:{false=[b, d, e], true=[aa, a]}
	 */
	public static void partitioningBy() {
		Map<Boolean, List<String>> rlt1 = dalplist.stream().collect(Collectors.partitioningBy(c -> c.contains("a")));
		Map<Boolean, Set<String>> rlt2 = dalplist.stream().collect(Collectors.partitioningBy(c -> c.contains("a"), Collectors.toSet()));
		System.out.println("partitioningBy-result1:" + rlt1);
		System.out.println("partitioningBy-result2:" + rlt2);
	}

	/**
	 * 入力リスト値からMap(キー:入力リスト値、値:入力文字インデックスに位置する数値文字列)を作成する。
	 * 最初のサンプルでは同キーの場合例外が発生。2ケース目のサンプルではコンマでマージする関数を指定している。
	 * Stream要素にもとづいてキーと値を設定し(同値キーを許可する場合は値マージ関数を指定)Mapを作成する。
	 * 出力結果:
	 * toMap-result1:{a=1, b=2, c=3, d=4, e=5}
	 * toMap-result2:{a=1&1, b=2, d=4, e=5}
	 */
	public static void toMap() {
		Map<String, String> rlt1 = alplist.stream().collect(Collectors.toMap(t -> t, t -> {return numlist.get(alplist.indexOf(t));}));
		Map<String, String> rlt2 = talplist.stream().collect(Collectors.toMap(t -> t, t -> {return numlist.get(alplist.indexOf(t));},
			(v1, v2) -> v1 + "&" + v2));
		System.out.println("toMap-result1:" + rlt1);
		System.out.println("toMap-result2:" + rlt2);
	}

	/**
	 * 入力リスト値から各種集計値(数、合計、最小値、平均値、最大値)を算出する。
	 * Stream要素の各種集計値((数、合計、最小値、平均値、最大値))を算出する。
	 * 出力結果:
	 * summarizingInt-result:IntSummaryStatistics{count=5, sum=15, min=1, average=3.000000, max=5}
	 */
	public static void summarizingInt() {
		IntSummaryStatistics rlt = numlist.stream().collect(Collectors.summarizingInt(t -> Integer.parseInt(t)));
		System.out.println("summarizingInt-result:" + rlt);
	}

	/**
	 * 入力リスト値(数値文字列)をインデックスとして他リストの文字列を取得して文字列配列Listを一旦作成し、
	 * そのList要素の2つの配列文字列を結合して再度Listを作成する。
	 * Stream要素(文字列配列)を格納場所を設定する関数、格納処理する関数、統合する関数を指定して
	 * 新たにListを作成する。
	 * 出力結果:
	 * size=5
	 * [a1, b2, aa3, d4, e5]
	 */
	public static void connectStringByList() {
		List<String[]> dataList = dalplist.stream().map(s -> {String[] ary = new String[2]; ary[0] = s;
			ary[1]=numlist.get(dalplist.indexOf(s)); return ary;}).collect(Collectors.toList());
		List<String> conList = dataList.stream().collect(() -> new ArrayList<String>(), (r, t) -> r.add(t[0] + t[1]),
			(r1, r2) -> r1.addAll(r2));
		System.out.println("connectStringArray-result:" + "\n size=" + conList.size() + "\n" + conList);
	}

	/**
	 * 入力リスト値(文字列)をStringBuilderで結合する。
	 * Stream要素(文字列)を格納場所を設定する関数、格納処理する関数、統合する関数を指定して
	 * 新たにStringBuilderを作成する。
	 * 出力結果:
	 * connectStringByStringBuilder-result:
	 * abaade
	 */
	public static void connectStringByStringBuilder() {
		StringBuilder sbrlt = dalplist.stream().collect(StringBuilder::new, (r, t) -> r.append(t), (r1, r2) -> r1.append(r2));
		System.out.println("connectStringByStringBuilder-result:" + "\n" + sbrlt);
	}
}

Streamクラスmap,flatMapメソッドのサンプル

StreamクラスのなかでもflatMapは理解しずらいメソッドなので少し具体的なサンプルをmapメソッドとともに紹介します。
flatMapメソッドについては対象がListの場合(listFlatMap)と配列の場合(arrayFlatMap)の2ケースを紹介しています。
また一般的に紹介されるような単純なListや配列ではなく、3重Listや3次元配列を対象としてより参考になるように配慮しています。
(単純なサンプルは第1部で紹介しています。)
両メソッドを簡潔に説明するとmapメソッドは1対1(入出力stream要素数比)の型変換を伴うStream型戻り処理で、flatMapメソッドは1対Nの型変換・フラット化を伴うStream型戻り処理ということになっています。
listFlatMapを例にすると、3つの文字列stream要素について最初のflatMapメソッドにおいて3重文字列リストを作成後streamメソッドで処理して2重文字列Listのstream(9要素)を戻し、その後2つのflatMapメソッドでもフラット化(多重→単一化、多次元→無次元化)をして、最終的に3つの文字列stream要素から生成した3重Listを36個の文字列stream要素に変換・作成をしています。
このようにflatMapは1対Nの型変換・フラット化を行うメソッドとなっています。

メソッド名 説明     
listMap  配列(文字列)から3重ListStreamを作成する。
listFlatMap  配列(文字列)から3重文字列リストを作成しこれを一旦2重文字列ListStreamにしその後単一文字列化してList(合計36値)を作成しこれをソートして結果を取得する。
arrayFlatMap  配列(文字列)から3次元文字列配列を作成しこれを一旦2次元文字列配列Streamにしその後単一文字列化してList(合計36値)を作成しこれをソートして結果を取得する。
public class FlatMapSample {
	private static StringBuilder sblist = new StringBuilder();
	private static int count;
	private static final String[] darray = {"a1", "b2", "c3"};
	private static final char[] firstChars= {'0', '1', '2'};
	private static final char[] secondChars = {'A', 'B'};
	private static final int numsepchar = 15*(5 + 2);

	public static void execute() {
		listMap();
		listFlatMap();
		arrayFlatMap();
	}

	/**
	 * 配列(文字列)から3重ListStreamを作成する。
	 * 配列からStreamを生成しこの各Stream要素1つに対し1つの3重リストStream(合計36個の値)を作成する。
	 * String型(3stream要素)からList<List<List<String>>>型(3stream要素)への1:1型変換
	 * List値はfirstChars[i]+secondChars[j]+k(0/0~1/0~2)+(a1/b2/c3)の1+1+1+2=5文字で構成。
	 * [i]=0~2(0/1/2)、[j]=0~1(A/B)、k=0/0~1/0~2
	 * 出力項目数は1番目Streamで3*2*1(0a1)=6、2番目Streamで3*2*2(0/1+b2)=12、3番目Streamで3*2*3(0/1/2+c3)=18
	 * 出力結果:
	 * 要素内容(Map-List):
	 * Stream出力:1
	 * 1:0A0a1 2:0B0a1 3:1A0a1 4:1B0a1 5:2A0a1 6:2B0a1
	 * Stream出力:2
	 * 1:0A0b2 2:0A1b2 3:0B0b2 4:0B1b2 5:1A0b2 6:1A1b2 7:1B0b2 8:1B1b2 9:2A0b2 10:2A1b2
	 * 11:2B0b2 12:2B1b2
	 * Stream出力:3
	 * 1:0A0c3 2:0A1c3 3:0A2c3 4:0B0c3 5:0B1c3 6:0B2c3 7:1A0c3 8:1A1c3 9:1A2c3 10:1B0c3
	 * 11:1B1c3 12:1B2c3 13:2A0c3 14:2A1c3 15:2A2c3 16:2B0c3 17:2B1c3 18:2B2c3
	 */
	public static void listMap() {
		System.out.println("要素合計:" + (1+2+3)*2*3);
		Stream<String> stream = Arrays.stream(darray);
		//String(3stream要素)→List<List<List<String>>>(3stream要素)
		Stream<List<List<List<String>>>> streamList = stream.map(v -> {
			int numRepeat = Integer.parseInt(v.substring(1));
			List<List<List<String>>> tlist = new ArrayList<List<List<String>>>();
			for (int i = 0; i < 3; i++) {
				List<List<String>> slist = new ArrayList<List<String>>();
				for (int j = 0; j < 2; j++) {
					List<String> flist = new ArrayList<String>();
					for (int k = 0; k < numRepeat; k++) {
						String val = String.valueOf(firstChars[i]) + String.valueOf(secondChars[j]) + String.valueOf(k) + v;
						flist.add(val);
					}
					slist.add(flist);
				}
				tlist.add(slist);
			}
			return tlist;
		});
		printStreamList(streamList, "要素内容(Map-List)");
	}

	/**
	 * 配列(文字列)から3重文字列リストを作成しこれを一旦2重文字列ListStreamにしその後単一文字列化してList
	 * (合計36値)を作成しこれをソートして結果を取得する。
	 * 配列からStreamを生成しこの各Stream要素1つに対し1つの3重文字列リスト(合計36個の値)を作成後、3回の
	 * streamメソッドを実行して単一文字列化してリスト化しこれをCollections.sortして結果を作成する。
	 * String型(3stream要素)から3重文字列リスト作成→stream(2重文字列リスト9stream要素)→2回flatMap(stream)
	 * 実施(2重文字列リスト9stream要素→ 1重文字列リスト18stream要素→文字列リスト36stream要素と変化)→
	 * List化→sortと処理を行い結果を取得。
	 * 1:N型変換のflatMapを3回実施して単一文字列化。
	 * List値はfirstChars[i]+secondChars[j]+k(0/0~1/0~2)+(a1/b2/c3)の1+1+1+2=5文字で構成。
	 * [i]=0~2(0/1/2)、[j]=0~1(A/B)、k=0/0~1/0~2
	 * 出力結果:
	 * 要素合計:36
	 * 0A0a1, 0A0b2, 0A0c3, 0A1b2, 0A1c3, 0A2c3, 0B0a1, 0B0b2, 0B0c3, 0B1b2, 0B1c3, 0B2c3, 1A0a1, 1A0b2, 1A0c3,
	 * 1A1b2, 1A1c3, 1A2c3, 1B0a1, 1B0b2, 1B0c3, 1B1b2, 1B1c3, 1B2c3, 2A0a1, 2A0b2, 2A0c3, 2A1b2, 2A1c3, 2A2c3,
	 * 2B0a1, 2B0b2, 2B0c3, 2B1b2, 2B1c3, 2B2c3
	 */
	public static void listFlatMap() {
		System.out.println("要素合計:" + (1+2+3)*2*3);
		Stream<String> stream = Arrays.stream(darray);
		//String(3stream要素)→2重文字列リスト(9stream要素)
		List<String> list = stream.flatMap(v -> {
			int numRepeat = Integer.parseInt(v.substring(1));
			List<List<List<String>>> tlist = new ArrayList<List<List<String>>>();
			for (int i = 0; i < 3; i++) {
				List<List<String>> slist = new ArrayList<List<String>>();
				for (int j = 0; j < 2; j++) {
					List<String> flist = new ArrayList<String>();
					for (int k = 0; k < numRepeat; k++) {
						String val = String.valueOf(firstChars[i]) + String.valueOf(secondChars[j]) + String.valueOf(k) + v;
						flist.add(val);
					}
					slist.add(flist);
				}
				tlist.add(slist);
			}
			return tlist.stream();
		//2重文字列リスト(9stream要素)→1重文字列リスト(18stream要素)
		}).flatMap(Collection::stream)
		//1重文字列リスト(18stream要素)→単一文字列(36stream要素)
		.flatMap(Collection::stream)
		.collect(Collectors.toList());
		Collections.sort(list);
		printList(list, "要素内容(List)");
	}

	/**
	 * 配列(文字列)から3次元文字列配列を作成しこれを一旦2次元文字列配列Streamにしその後単一文字列化してList
	 * (合計36値)を作成しこれをソートして結果を取得する。
	 * 配列からStreamを生成しこの各Stream要素1つに対し1つの3次元文字列配列(合計36個の値)を作成後、3回の
	 * streamメソッドを実行して単一文字列化してリスト化しこれをCollections.sortして結果を作成する。
	 * String型(3stream要素)から3次元文字列配列作成→stream(2次元文字列配列9stream要素)→2回flatMap(stream)
	 * 実施(2次元文字列配列9stream要素→ 1次元文字列配列18stream要素→単一文字列36stream要素と変化)→
	 * List化→sortと処理を行い結果を取得。
	 * 1:N型変換のflatMapを3回実施して単一文字列化。
	 * List値はfirstChars[i]+secondChars[j]+k(0/0~1/0~2)+(a1/b2/c3)の1+1+1+2=5文字で構成。
	 * [i]=0~2(0/1/2)、[j]=0~1(A/B)、k=0/0~1/0~2
	 * 出力結果:
	 * 要素合計:36
	 * 0A0a1, 0A0b2, 0A0c3, 0A1b2, 0A1c3, 0A2c3, 0B0a1, 0B0b2, 0B0c3, 0B1b2, 0B1c3, 0B2c3, 1A0a1, 1A0b2, 1A0c3,
	 * 1A1b2, 1A1c3, 1A2c3, 1B0a1, 1B0b2, 1B0c3, 1B1b2, 1B1c3, 1B2c3, 2A0a1, 2A0b2, 2A0c3, 2A1b2, 2A1c3, 2A2c3,
	 * 2B0a1, 2B0b2, 2B0c3, 2B1b2, 2B1c3, 2B2c3
	 */
	public static void arrayFlatMap() {
		System.out.println("要素合計:" + (1+2+3)*2*3);
		Stream<String> stream = Arrays.stream(darray);
		//String(3stream要素)→String[][](9stream要素)
		//[3]配列をstream化→[2][1]/[2][2]/[2][3]の2次元配列
		List<String> list =  stream.flatMap(v -> {
			int numRepeat = Integer.parseInt(v.substring(1));
			String[][][] array = new String[3][2][numRepeat];
			for (int i = 0; i < numRepeat; i++) {
				array[0][0][i] = String.valueOf(firstChars[0]) + String.valueOf(secondChars[0]) + String.valueOf(i) + v;
				array[1][0][i] = String.valueOf(firstChars[1]) + String.valueOf(secondChars[0]) + String.valueOf(i) + v;
				array[2][0][i] = String.valueOf(firstChars[2]) + String.valueOf(secondChars[0]) + String.valueOf(i) + v;
				array[0][1][i] = String.valueOf(firstChars[0]) + String.valueOf(secondChars[1]) + String.valueOf(i) + v;
				array[1][1][i] = String.valueOf(firstChars[1]) + String.valueOf(secondChars[1]) + String.valueOf(i) + v;
				array[2][1][i] = String.valueOf(firstChars[2]) + String.valueOf(secondChars[1]) + String.valueOf(i) + v;
			}
			return Arrays.stream(array);
		//String[][](9stream要素)→String[](18stream要素)
		//[3][2]配列をstream化→[1]/[2]/[3]の1次元配列
		}).flatMap(ary ->{return Arrays.stream(ary);})
		//String[]18stream要素→String(36stream要素)
		//[3][2][1or2or3]配列をstream化→単一文字列
		.flatMap(ary ->{return Arrays.stream(ary);})
		.collect(Collectors.toList());
		Collections.sort(list);
		printList(list, "要素内容(Flatmap-String)");
	}

	/**
	 * 3重文字列リストstreamの内容を出力する。
	 */
	public static void printStreamList(Stream<List<List<List<String>>>>streamList, String title) {
		count = 0;
		System.out.println(title + ":");
		streamList.forEach(sl -> {
			++count;
			System.out.println("Stream出力:" + count);
			int icount = 0;
			for (int i = 0; i < sl.size(); i++) {
				int leni = sl.get(i).size();
				for (int j = 0; j < leni; j++) {
					int lenj = sl.get(i).get(j).size();
					for (int k = 0; k < lenj; k++) {
						++icount;
						String outstr = sl.get(i).get(j).get(k);
						System.out.println(icount + ":" + outstr);
					}
				}
			}
		});
	}

	/**
	 * 文字列リスト(36値)を1行あたりの項目数を制限して出力する。
	 */
	public static void printList(List<String> list, String title) {
		sblist.setLength(0);
		list.stream().forEach(v -> {
			sblist.append(v);
			sblist.append(", ");
		});
		String str = sblist.toString();
		int numsep = (str.length() / numsepchar) + 1;
		String outstr = null;
		for (int i = 0; i < numsep; i++) {
			int sind = i * numsepchar;
			int eind = Math.min(sind+numsepchar-1, str.length()-1);
			if ((numsep-1) == i) {
				outstr = str.substring(sind, eind-1);
			} else if (0 == i) {
				outstr = str.substring(sind, eind);
			} else {
				outstr = str.substring(sind, eind);
			}
			System.out.println("    " + outstr);
		}
	}
}

まとめ

Javaでの関数プログラミング(API)は主としてコレクション(Collection)やMapを対象としており広範囲なものではありませんが関連するメソッド数はかなりの数になります。
これらAPIを使用すればJavaにおいて種々な関数プログラミングができることになりますが、第1部や第2部のまとめでも記述していますが、あえて難度の高い手法やアルゴリズムは採用せず、Javaで標準的に用意されているAPIを使用する範囲に留めて、パイプラインが2行程度で収まらない処理規模の場合は従来の方法(命令型スタイル、for文など)で記述した方が適当ではないかと判断しています。
コーディング量は少し減るものの、あえて長々とパイプラインを記述するとかえって読みにくくなると考えています。

最後に

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

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