[Java] 20. iterator(for-each)とStream APIを使う方法
こんにちは。明月です。
この投稿はJavaでiterator(for-each)とStream APIを使う方法に関する説明です。
オブジェクト指向(OOP)でデータをオブジェクト化すると、よく使うアルゴリズムはListとMapだと思います。
ListとMapをよく使う理由はデータの探索がしやすいし整列、データ分類などがしやすいからだと思います。
Listを探索する時にfor文などを使ってi=0からsizeまで比較する方法でよく使うと思います。
import java.util.ArrayList;
import java.util.List;
// Nodeクラス
class Node {
// メンバー変数
private int data;
// コンストラクタ
public Node(int data) {
this.data = data;
}
// メンバー変数をリターン
public int getData() {
return this.data;
}
// メンバー変数の値を格納
public void setDate(int data) {
this.data = data;
}
}
// 実行クラス
public class Example {
// 実行関数
public static void main(String... args) {
// 0から9までのデータがあるNodeクラスのリスト
List<Node> list = new ArrayList<>();
// 0から9まで繰り返し
for (int i = 0; i < 10; i++) {
// Nodeインスタンスを生成
list.add(new Node(i));
}
// 奇数値だけリストから抽出して格納するリスト
List<Node> odds = new ArrayList<>();
// listのサイズまで繰り返し
for (int i = 0; i < list.size(); i++) {
// Nodeインスタンスを取得
Node node = list.get(i);
// Nodeのdataが奇数のことだけ
if (node.getData() % 2 == 1) {
// 奇数リストに格納
odds.add(node);
}
}
// 奇数リストのサイズまで繰り返し
for (int i = 0; i < odds.size(); i++) {
// コンソールに出力
System.out.println(odds.get(i).getData());
}
}
}
リストにデータを格納して奇数のデータはfor文とif文を組み合わせて抽出しました。
このリスト形式のデータはデザインパターンによるiteratorパターンになってnextとhasの関数を利用してデータを取得することができます。
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
// Nodeクラス
class Node {
// メンバー変数
private int data;
// コンストラクタ
public Node(int data) {
this.data = data;
}
// メンバー変数をリターン
public int getData() {
return this.data;
}
// メンバー変数の値を格納
public void setDate(int data) {
this.data = data;
}
}
// 実行クラス
public class Example {
// 実行関数
public static void main(String... args) {
// 0から9までのデータがあるNodeクラスのリスト
List<Node> list = new ArrayList<>();
// 0から9まで繰り返し
for (int i = 0; i < 10; i++) {
// Nodeインスタンスを生成
list.add(new Node(i));
}
// 奇数値だけリストから抽出して格納するリスト
List<Node> odds = new ArrayList<>();
// listのIteratorを取得
Iterator<Node> iter= list.iterator();
// 次のポインタに値があるか?
while(iter.hasNext()) {
// 次のポインタの値を取得する。
Node node = iter.next();
// Nodeのdataが奇数のことだけ
if (node.getData() % 2 == 1) {
// 奇数リストに格納
odds.add(node);
}
}
// oddsのIteratorを取得
iter = odds.iterator();
// 次のポインタに値があるか?
while(iter.hasNext()) {
// 次のポインタの値を取得する。
Node node = iter.next();
// コンソールに出力
System.out.println(node.getData());
}
}
}
for文ではなく、while文を使って繰り返しの形が変わりましたが、ステップが減らしたことではないです。逆にlist.iterator()の処理項目が増えてもっと難しいプログラムみたいに見えます。
でも、for(for-each)文を使って簡単に作成することができます。
import java.util.ArrayList;
import java.util.List;
// Nodeクラス
class Node {
// メンバー変数
private int data;
// コンストラクタ
public Node(int data) {
this.data = data;
}
// メンバー変数をリターン
public int getData() {
return this.data;
}
// メンバー変数の値を格納
public void setDate(int data) {
this.data = data;
}
}
// 実行クラス
public class Example {
// 実行関数
public static void main(String... args) {
// 0から9までのデータがあるNodeクラスのリスト
List<Node> list = new ArrayList<>();
// 0から9まで繰り返し
for (int i = 0; i < 10; i++) {
// Nodeインスタンスを生成
list.add(new Node(i));
}
// 奇数値だけリストから抽出して格納するリスト
List<Node> odds = new ArrayList<>();
// whileのiteratorパターンよりもっと簡単に作成することができる。
for(Node node : list) {
// Nodeのdataが奇数のことだけ
if (node.getData() % 2 == 1) {
// 奇数リストに格納
odds.add(node);
}
}
// whileのiteratorパターンよりもっと簡単に作成することができる。
for(Node node : odds) {
// コンソールに出力
System.out.println(node.getData());
}
}
}
リストをfor文で探索すると初期値や増加値がいらないです。つまり、もっと簡単に作成することができるという意味です。
Stream APIに関して説明します。
Streamという意味はプログラムでは連続的な値の配列の意味です。
例えば、一つの動画のファイルを見るときに一つのbyteの値を見れば全然意味がないですね。でもそのbyteのデータが集まって一つの意味があるファイルになって動画がプレイーすることができます。
Javaにはこの連続なデータつまり、配列やList、Mapをもっとしやすく使えるAPI(Application provider interface)がありますが、それがStream APIです。
上の例でListからDataが奇数値だけ分類する時にfor-each文を使ってifで比較して格納しました。
これをStreamのfilterを使えば簡単になります。
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
// Nodeクラス
class Node {
// メンバー変数
private int data;
// コンストラクタ
public Node(int data) {
this.data = data;
}
// メンバー変数をリターン
public int getData() {
return this.data;
}
// メンバー変数の値を格納
public void setDate(int data) {
this.data = data;
}
}
// 実行クラス
public class Example {
// 実行関数
public static void main(String... args) {
// 0から9までのデータがあるNodeクラスのリスト
List<Node> list = new ArrayList<>();
// 0から9まで繰り返し
for (int i = 0; i < 10; i++) {
// Nodeインスタンスを生成
list.add(new Node(i));
}
// Stream式に変換
Stream<Node> stream = list.stream();
// listの値をフィルターする。
stream = stream.filter(x -> x.getData() % 2 == 1);
// 結果はlistタイプに返却する。
List<Node> odds = stream.collect(Collectors.toList());
// oddsリストをStream式に変換して繰り返し呼び出す。
odds.stream().forEach(x -> {
// コンソールに出力
System.out.println(x.getData());
});
}
}
ソースのステップが最初より確実に減らしました。可読性もよくなりました。返却値も新しいリストでリターンするので、元のデータにも影響がありません。
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
// Nodeクラス
class Node {
// メンバー変数
private int data;
// コンストラクタ
public Node(int data) {
this.data = data;
}
// メンバー変数をリターン
public int getData() {
return this.data;
}
// メンバー変数の値を格納
public void setDate(int data) {
this.data = data;
}
}
// 実行クラス
public class Example {
// 実行関数
public static void main(String... args) {
// 0から9までのデータがあるNodeクラスのリスト
List<Node> list = new ArrayList<>();
// 0から9まで繰り返し
for (int i = 0; i < 10; i++) {
// Nodeインスタンスを生成
list.add(new Node(i));
}
// チェインメソッドパタンタイプで次々に連結することができて、一つのデータを取得することができます。
Optional<Node> op = list.stream()
.filter(x -> x.getData() % 2 == 1)
.filter(x -> x.getData() % 3 == 0)
.filter(x -> x.getData() > 5)
.findFirst();
// 一つのデータを取得する時にはOptionalタイプで取得する。
// isPresentは値があるか?isEmptyは値がないか?チェックをしてgetでデータを取得する。
if(op.isPresent()) {
// コンソールに出力
System.out.println(op.get().getData());
}
}
}
Stream式はチェインメソッドパターン式でStream式を再使用することが可能し、filter関数を何回も付けることができます。
import java.util.ArrayList;
import java.util.List;
// Nodeクラス
class Node {
// メンバー変数
private int data;
// コンストラクタ
public Node(int data) {
this.data = data;
}
// メンバー変数をリターン
public int getData() {
return this.data;
}
// メンバー変数の値を格納
public void setDate(int data) {
this.data = data;
}
}
// 実行クラス
public class Example {
// 実行関数
public static void main(String... args) {
// 0から9までのデータがあるNodeクラスのリスト
List<Node> list = new ArrayList<>();
// 0から9まで繰り返し
for (int i = 0; i < 10; i++) {
// Nodeインスタンスを生成
list.add(new Node(i));
}
// 並列処理が可能なStream式
list.parallelStream().forEach(x -> {
// データに10を掛けて格納
x.setDate(x.getData() * 10);
// コンソールに出力
System.out.println(x.getData());
});
// 改行
System.out.println();
// ソート、降順
list.stream().sorted((x, y) -> {
// sortのリターンタイプはintタイプです。-1になると降順、1になると昇順
return Integer.compare(y.getData(), x.getData());
}).forEach(x -> {
// コンソールに出力
System.out.println(x.getData());
});
}
}
並列処理はスレッドと関係がある部分です。
簡単に説明するとfor文でリストを処理する時に順番とおりにget(0)からget(list.size()-1)まで取得することができます。
でも、仕様によって順番通りに処理する必要がなくて、並列に処理してパフォーマンス改善する時があります。StreamのパラレルスレッドはPCの性能によって自動に生成します。でも、順番通りに処理することではなく、同時に処理することなので、順番が必要な処理では問題がなる可能性があります。
Stream式はsort関数もあります。ただ、二つの値をCompare関数を利用して昇順、降順で並べることもできます。
link - [Java] Compare関数を使う方法
import java.util.ArrayList;
import java.util.List;
// Nodeクラス
class Node {
// メンバー変数
private int data;
// コンストラクタ
public Node(int data) {
this.data = data;
}
// メンバー変数をリターン
public int getData() {
return this.data;
}
// メンバー変数の値を格納
public void setDate(int data) {
this.data = data;
}
}
// 実行クラス
public class Example {
// 実行関数
public static void main(String... args) {
// 0から9までのデータがあるNodeクラスのリスト
List<Node> list = new ArrayList<>();
// 0から9まで繰り返し
for (int i = 0; i < 10; i++) {
// Nodeインスタンスを生成
list.add(new Node(i));
}
// Nodeのdataで10の値より大きい値があるか?
if (!list.stream().anyMatch(x -> x.getData() > 10)) {
// コンソールに出力
System.out.println("The data is not have it.");
}
}
}
listに値があるかどうかも確認できます。元々、Listのクラスにはcontains関数があります。
contains関数にはただイコール(=)のデータだけ検索するので、原始データタイプはできるかもしれませんが、クラスタイプは比較ができないです。なのでStream APIのanyMatchを利用すれば簡単にできます。
Stream APIの関数はすごく多いです。
それを全部ここで説明しようと思えば無理です。なので実務でよく使うもの(filter、sorted、findFirst、anyMatch)を説明しました。
もしかして、その以上の関数の使い方を知りたいなら下記のJavadocを確認してください。
link - https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html
ここまでJavaでiterator(for-each)とStream APIを使う方法に関する説明でした。
ご不明なところや間違いところがあればコメントしてください。
- [Java] 27. ネットワーク通信(Socket)をする方法2019/09/16 23:42:46
- [Java] 26. ファイル(IO)を扱う方法(ファイル作成、ファイル修正、アクセス日付変更とIOをclose(リソース返却)する理由、Closableインタフェース)2019/09/13 20:03:58
- [Java] 25. Objectクラス(notify、waitの使い方)2019/09/13 00:58:31
- [Java] 24. Javaの同期化(Synchronized)とデッドロック(Deadlock)2019/09/11 23:06:09
- [Java] 23. スレッドプール(Threadpool)を使う方法2019/09/10 21:55:36
- [Java] 22.スレッド(Thread)を使う方法2019/09/06 22:30:49
- [Java] 21. アノテーション(Annotation)を使う方法2019/09/05 22:58:20
- [Java] 20. iterator(for-each)とStream APIを使う方法2019/09/04 20:11:28
- [Java] 19. ラムダ(Lambda)を使う方法2019/09/03 20:37:14
- [Java] 18. 匿名クラス(Anonymous class)とクロージャ(closure)2019/09/02 20:30:34
- [Java] 17. ジェネリックタイプ(Generic type)を使う方法2019/08/27 19:05:44
- [Java] 16. 例外処理(try~catch~finally, throw)を使う方法2019/08/26 23:40:29
- [Java] 15. 列挙型(バイナリデータビット演算子の使用例)2019/08/23 19:46:10
- [Java] 14. オブジェクト指向プログラミング(OOP)の4つ特性(カプセル化、抽象化、継承、多相化)2019/08/22 20:08:37
- [Java] 13. 抽象クラス(abstract)と継承禁止(final)2019/08/22 00:06:20
- check2024/04/10 19:03:53
- [Java] 64.Spring bootとReactを連結する方法(Buildする方法)2022/03/25 21:02:18
- [Javascript] Node.jsをインストールしてReactを使う方法2022/03/23 18:01:34
- [Java] 63. Spring bootでcronスケジューラとComponentアノテーション2022/03/16 18:57:30
- [Java] 62. Spring bootでWeb-Filterを設定する方法(Spring Security)2022/03/15 22:16:37
- [Java] JWT(Json Web Token)を発行、確認する方法2022/03/14 19:12:58
- [Java] 61. Spring bootでRedisデータベースを利用してセッションクラスタリング設定する方法2022/03/01 18:20:52
- [Java] 60. Spring bootでApacheの連結とロードバランシングを設定する方法2022/02/28 18:45:48
- [Java] 59. Spring bootのJPAでEntityManagerを使い方2022/02/25 18:27:48
- [Java] 58. EclipseでSpring bootのJPAを設定する方法2022/02/23 18:11:10
- [Java] 57. EclipseでSpring bootを設定する方法2022/02/22 19:04:49
- [Python] Redisデータベースに接続して使い方2022/02/21 18:23:49
- [Java] Redisデータベースを接続して使い方(Jedisライブラリ)2022/02/16 18:13:17
- [C#] Redisのデータベースを接続して使い方2022/02/15 18:46:09
- [CentOS] Redisデータベースをインストールする方法とコマンドを使い方2022/02/14 18:33:07