JavaでStreamを使う(サンプルコード有り)

Java_icon Java
この記事は約13分で読めます。

この記事は、以下の方を対象に書いています。

  • Streamを使ったサンプルコードが知りたい
  • Streamの便利な使い方や相性の良いメソッドを知りたい

Streamについて

Streamは繰り返しの処理をしたり、条件をつけたり、並び替えをしたりと

様々な処理をシンプル(場合によっては複雑)に記述することができます。

例えば、以下のコードでは、Listの中に1~10までの値を格納し、2の倍数だけを表示するものです。

List<Integer> list = IntStream.rangeClosed(1, 10)
				.mapToObj(i -> Integer.valueOf(i))
				.filter(f -> f % 2 == 0)
				.collect(Collectors.toList());
		
System.out.println(list);
// [2, 4, 6, 8, 10]

List型について知りたい方は、以下の記事も併せて読んでみてください。

Streamのメリット

Streamは、とても便利でJavaで作られたシステムであれば、ほぼ確実に使われていると思います。

ただ、Java初学者にとっては、少し特殊な書き方をするし、Streamを使わなくても、

再現することはできるので、敬遠している方も少なくないかと思います。

ですので、ここではStreamのメリットについて解説をします。

簡潔さと可読性

Streamを使う場合、filterメソッドやsortメソッド、toUpperCaseメソッドなどが、

デフォルトで用意されていてます。

そのため、和訳すると何の処理をしているかの予想をすることができます。

Streamを使わず、for文等で代用した場合、プログラマーの意図は伝わりづらいです。

これが、可読性が高いと言われる理由の一つです。

その他にも、コード量が少なくなり、理解や保守が容易になります。

例えば、複数個ある文字の中から指定のものを見つけたいときに、

Streamを使う場合と、使わない場合で比較してみます。

以下は、Streamを使わない場合です。

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class Sample1 {
	public static void main(String[] args) {
		List<String> list = new ArrayList<>();
		Collections.addAll(list, "apple", "banana", "orange");
		
		for (String val : list) {
			if ("banana".equals(val)) {
				System.out.println(val + "みっけ!");
			}
		}
	}
}

次は、Streamを使う場合です。

public class Sample1 {
	public static void main(String[] args) {
		List<String> list = new ArrayList<>();
		Collections.addAll(list, "apple", "banana", "orange");
		
		list.stream()
			.filter(f -> "banana".equals(f))
			.findFirst()
			.ifPresent(item -> System.out.println(item + "みっけ!"));
	}
}

Streamを使う場合は、見やすくするために改行していますが、

1行で書くことができるようになりました。

また、各メソッドを見たときに、

filter(探す)して、

findFirst(条件に一致した最初を取得)して、

ifPresent(条件に一致した場合にコンソール表示)すると一目で理解することができます。

※ただし、慣れは必要です。

柔軟性と拡張性

Streamは、非常に便利で、前述しましたがデフォルトで用意されているメソッド達が

非常に優秀で大体のことが実現できます。

また、filterメソッドやsortメソッドなどのデータを加工したり、制限をかけたりする

メソッドのことを中間操作と言います。

中間操作は、チェイン可能(ドットで繋げること)で、複数の中間操作を

組み合わせて使うことができるので、複雑な条件設定などが可能になります。

また、forEachメソッドのように最終的な処理を行うメソッドを終端操作と言います。

この中間操作と終端操作を自由に組み合わせることで、柔軟に対処できます。

また、中間操作や拡張操作は、自分の好きなように拡張できる点も、

Streamが便利といわれる理由です。

サンプルコード

ここからはサンプルコードの紹介をします。

大文字に変換して出力

Listに格納したString型の値を全て大文字に変換(toUpperCaseメソッド)して出力します。

ちなみに小文字にしたい場合はtoLowerCaseメソッドを使用します。

package java20230513;

import java.util.ArrayList;
import java.util.List;

public class Sample1 {
	public static void main(String[] args) {
		// String型のListを準備し、値を格納
		List<String> list = new ArrayList<>();
		list.add("aaa");
		list.add("bbb");
		list.add("ccc");
		list.add("ddd");
		list.add("eee");
		
		// この時点のList
		System.out.println(list);
		// [aaa, bbb, ccc, ddd, eee]
		
		// Listに格納された値を全て大文字に変換して出力
		list.stream().map(m -> m.toUpperCase()).forEach(System.out::print);
		// AAABBBCCCDDDEEE
	}
}

上記のサンプルコードを少し改造します。

今は21行目の戻り値型はvoid(戻り値なし)になります。

ただ、大文字に変換した状態でListに入れておきたい場合は以下のように記述します。

package java20230513;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

public class Sample1 {
	public static void main(String[] args) {
		// String型のListを準備し、値を格納
		List<String> list = new ArrayList<>();
		list.add("aaa");
		list.add("bbb");
		list.add("ccc");
		list.add("ddd");
		list.add("eee");
		
		// この時点のList
		System.out.println(list);
		// [aaa, bbb, ccc, ddd, eee]
		
		// Listに格納された値を全て大文字に変換して出力
		List<String> newList = list.stream().map(m -> m.toUpperCase()).collect(Collectors.toList());
		System.out.println(newList);
		// [AAA, BBB, CCC, DDD, EEE]
	}
}

上記のようにforEachメソッドを使わない場合は戻り値はStream型になるため、

Collectors.toListメソッドでList型に変換します。

特定の条件をつける

次は、String型の値が格納されたListの中から5文字以上のものだけのListを作ります。

21行目のfilterメソッドで条件を付けています。

ちなみに、filterメソッドは、条件に当てはまるデータが複数になる可能性があるため、

List型に変換しています。

package java20230513;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

public class Sample2 {
	public static void main(String[] args) {
		// String型のListを準備し、値を格納
		List<String> list = new ArrayList<>();
		list.add("aaa"); // 3文字
		list.add("bbbb"); // 4文字
		list.add("ccccc"); // 5文字
		list.add("dddddd"); // 6文字
		list.add("eeeeeee"); // 7文字
		
		System.out.println(list);
		// [aaa, bbbb, ccccc, dddddd, eeeeeee]
		
		List<String> newList = list.stream()
				.filter(f -> f.length() >= 5)
				.collect(Collectors.toList());
		System.out.println(newList);
		// [ccccc, dddddd, eeeeeee]
	}
}

オブジェクトを扱う

次は、オブジェクトをStreamで操作してみます。

Sampleというクラスを作成して、val1が「Tom」の場合のみ処理を行うサンプルコードです。

package training_20240626;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class Main {
	public static void main(String[] args) {
	Sample sample1 = new Sample("Tom", "Cat");
	Sample sample2 = new Sample("Jon", "Dog");
	Sample sample3 = new Sample("Tom", "Bird");
	
	List<Sample> sampleList = new ArrayList<>();
	Collections.addAll(sampleList, sample1, sample2, sample3);
	
	sampleList.stream()
		.filter(f -> "Tom".equals(f.getVal1()))
		.forEach(ff -> System.out.println(ff.getVal1() + "は" + ff.getVal2() + "が好き"));
	}
}

class Sample {
	private String val1;
	private String val2;
	
	public Sample(String val1, String val2) {
		this.val1 = val1;
		this.val2 = val2;
	}
	
	public String getVal1() {
		return val1;
	}
	public void setVal1(String val1) {
		this.val1 = val1;
	}
	public String getVal2() {
		return val2;
	}
	public void setVal2(String val2) {
		this.val2 = val2;
	}
}

このように、クラスのフィールドの値によって、処理内容を分岐させることも

Streamを使えば容易になります。

Stream内でインデックス管理

次は、Stream内でインデックスを取得したいときは、以下のようにします。

Streamを使う前に初期値を決めておいて、

Streamの中で値の操作ができるようになります。

これが意外と便利で、痒いところに手が届く感覚になれます。

package training_20240626;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

public class Main {
	public static void main(String[] args) {
	Sample sample1 = new Sample("Tom", "Cat");
	Sample sample2 = new Sample("Jon", "Dog");
	Sample sample3 = new Sample("Tom", "Bird");
	
	List<Sample> sampleList = new ArrayList<>();
	Collections.addAll(sampleList, sample1, sample2, sample3);
	
	AtomicInteger index = new AtomicInteger(0);
	sampleList.stream()
		.map(m -> {
			m.setVal0(index.incrementAndGet());
			return m;
		})
		.forEach(ff -> System.out.println(ff.getVal0() + ":" + ff.getVal1() + "は" + ff.getVal2() + "が好き"));
	}
    // 1:TomはCatが好き
    // 2:JonはDogが好き
    // 3:TomはBirdが好き
}

class Sample {
	private Integer val0;
	private String val1;
	private String val2;
	
	public Sample(String val1, String val2) {
		this.val1 = val1;
		this.val2 = val2;
	}
	
	public Integer getVal0() {
		return val0;
	}
	public void setVal0(Integer val0) {
		this.val0 = val0;
	}
	public String getVal1() {
		return val1;
	}
	public void setVal1(String val1) {
		this.val1 = val1;
	}
	public String getVal2() {
		return val2;
	}
	public void setVal2(String val2) {
		this.val2 = val2;
	}
}

最後に

Streamの使い方を紹介しました。

使い方を覚えると、色んな処理を書けるようになりますので、練習してみてください。

ここまで読んでいただきありがとうございます。

コメント

タイトルとURLをコピーしました