Java

JavaのStreamを利用するときの処理の共通化

こんにちは、ともです。

JavaでStreamを利用する際にメソッドに分けたり、ラッパーメソッドを作成すると処理を共通化できて良いと思ったので備忘を残します。状況によると思うので、色んな書き方を覚えておきたい。

サンプル1

それでは、以下のようなケースを考えます。「名前_年齢」というフォーマットで格納された文字列配列が存在するとします。その中から指定の年齢上の物を抽出します。フォーマットと異なった場合は例外処理を行いfalseをリターンしています。

public static void main(String[] args) {
		// data
		String[] students = new String[] {"Tom_21", "15_Jhon", "Tim_32", "Mark_92"};

		// 指定年齢以上のデータを抽出
		Arrays.asList(students).stream()
				.filter(str -> {
					try {
						int age = Integer.parseInt(str.split("_"));
						return age>=22;
					} catch(NumberFormatException e) {
						return false;
					}
				})
				.forEach(System.out::println);
	}

上記では22以上のもの抽出しました。可読性が低い。

Predicateを返却するメソッドにする(方法1)

それではサンプル1をもう少し汎用的にしたい+読みやすくしたいので、以下のようにPredicateを返却するメソッドとして定義します。

public static void main(String[] args) {
		// data
		String[] students = new String[] {"Tom_21", "Jhon_15", "Tim_32", "Mark_92"};

		// 指定年齢以上のデータを抽出
		Arrays.asList(students).stream()
				.filter(ageMoreThan(22))
				.forEach(System.out::println);
}

	/**
	 * Name_Age形式のデータから年齢を取得し、引数の年齢以上かチェックするPredicate
	 * @param targetAge
	 * @return
	 */
	private static Predicate<String> ageMoreThan(int targetAge) {
		return str -> {
			int age = Integer.parseInt(str.split("_"));
			return age>=targetAge;
		};
	}

そのままStreamに記載していた時と比べて読みやすくなりました。ageMoreThan(22)というメソッド名と引数の値から22歳以上を取得していることが直感的に分かります。また指定年齢上判定を別の所でも再利用可能であり、年齢も引数として設定可能です。

サンプル2

次にサンプリ1を少し変え、例外的なフォーマットの場合にfalseを返すのではなく、システムとして共通的な例外処理をしたいというケース。

public static void main(String[] args) {
		// data
		String[] students = new String[] {"Tom_21", "15_Jhon", "Tim_32", "Mark_92"};

		// 指定年齢以上のデータを抽出
		Arrays.asList(students).stream()
				.filter(wrapper(ageMoreThan(22)))
				.forEach(System.out::println);
	}

	/**
	 * Name_Age形式のデータから年齢を取得し、引数の年齢以上かチェックするPredicate
	 * @param targetAge
	 * @return
	 */
	private static Predicate<String> ageMoreThan(int targetAge) {
		return str -> {
			try {
				int age = Integer.parseInt(str.split("_"));
				return age>=targetAge;
			} catch(NumberFormatException e) {
				throw new ThisSystemException(e);
			}
		};
	}

このように例外発生時はこのシステムの例外で包んでリスローすれば良さそう。しかし、この処理をStreamでメソッド化したいろいろな場所で書くのは大変。

Wrapperメソッドで例外処理を共通化する(方法2)

サンプル2に対する方法として、例外処理を行うラッパーメソッドを用意してみました。

public static void main(String[] args) {
		// data
		String[] students = new String[] {"Tom_21", "15_Jhon", "Tim_32", "Mark_92"};

		// 指定年齢以上のデータを抽出
		Arrays.asList(students).stream()
				.filter(wrapper(ageMoreThan(22)))
				.forEach(System.out::println);
	}

	/**
	 * Name_Age形式のデータから年齢を取得し、引数の年齢以上かチェックするPredicate
	 * @param targetAge
	 * @return
	 */
	private static Predicate<String> ageMoreThan(int targetAge) {
		return str -> {
			try {
				int age = Integer.parseInt(str.split("_"));
				return age>=targetAge;
			} catch(NumberFormatException e) {
				throw new ThisSystemException(e);
			}
		};
	}
	
	/**
	 * Predicate<String>実行のWrapperクラス
	 * 例外発生時本システム例外としてリスローする。
	 * @param predicate
	 * @return
	 * @throws ThisSystemException
	 */
	private static Predicate<String> wrapper(Predicate<String> predicate) throws ThisSystemException {
		return str -> {
			try {
				return predicate.test(str);
			} catch(Exception e) {
				throw new ThisSystemException(e);
			}
		};
	}

wrapperメソッドを間に挟むことで例外処理を共通化できる。色々な所で例外処理をするのを共通化できそう。

まとめ

関数型インタフェースを返却するメソッドを作成したり、実行をラップするメソッドを定義することで可読性をあげたり、共通化していきたいです。