Slide 1

Slide 1 text

あつめたデータをどう扱うか Java in the Box 櫻庭 祐一

Slide 2

Slide 2 text

Agenda • コレクションと繰り返し処理 • 3種類のループ • 特定用途に使用するコレクション • 並列/並行処理でのコレクション

Slide 3

Slide 3 text

櫻庭 祐一 • Java歴 27年 • Javaに関する著作多数 • WEB+DB Press連載 「見なおそう! モダンJavaの流儀」 • 書籍「Java SE 7/8 速攻入門」 et al. • 日本で1人目のJava Champion • JJUG創設メンバー

Slide 4

Slide 4 text

コレクションと繰り返し処理

Slide 5

Slide 5 text

コレクションでデータをまとめる… • とはいうものの、まとめただけでは意味がない • まとめたデータをどうするのか • 特にリストではまとめたデータに対して処理を加えることが多い

Slide 6

Slide 6 text

データの利用例 平均を求める • リストに4人の成績がまとめられているので、 平均点を求める • 4人だからいいけど、人数が増えたら? • インデックス書き間違えない? • 人数が増えたとき人数を更新しわすれない? List scores = ...; int sum = 0; sum += scores.get(0); sum += scores.get(1); sum += scores.get(2); sum += scores.get(3); double ave = (double)sum/4;

Slide 7

Slide 7 text

List scores = ...; int sum = 0; sum += scores.get(0); sum += scores.get(1); sum += scores.get(2); sum += scores.get(3); double ave = (double)sum/4; データの利用例 平均を求める • リストに4人の成績がまとめられているので、 平均点を求める インデックスが異なるだけで 同じ処理を繰り返している 処理をくくりだして ループでまとめる

Slide 8

Slide 8 text

平均を求める - for文バージョン ループ変数 i の制御 0からコレクションの要素数まで1ずつ増える ループ変数をインデックスに使用 コレクションの要素数を使用 List scores = ...; int sum = 0; for (int i = 0; i < scores.size(); i++) { sum += scores.get(i); } double ave = (double)sum/scores.size();

Slide 9

Slide 9 text

コレクションとループ Collection 処理 Collection 処理 Collection 処理 処理 値 Collection Collection Ex 画面表示 ファイル書き込み Ex 統計処理 検索 Ex 変換/マッピング テーブル操作 Ex ファイル読み込み

Slide 10

Slide 10 text

例 移動平均を求める • 1時間ごとの温度データを保持したコレクションから 直近n時間の移動平均を求める List calcMovingAveTemps(List temps, int n) { List movingAveTemps = new ArrayList<>(); for (int i = n-1; i < temps.size(); i++) { var movingSum = 0.0; for (int j = 0; j < n; j++) { movingSum += temps.get(i - j); } movingAveTemps.add(movingSum/n); } return movingAveTemps; }

Slide 11

Slide 11 text

for文の特徴 • ループの制御を自分で行う必要がある • インデックスを自由に扱うことができる • 行列、テンソルなどのデータ構造も扱いやすい • for文は文であるため、ループの処理結果を直接返せない • 結果を保持させる変数をループの外側で定義する必要がある

Slide 12

Slide 12 text

for文の特徴 • ループの制御を自分で行う必要がある • インデックスを自由に扱うことができる • 行列、テンソルなどのデータ構造も扱いやすい • for文は文であるため、ループの処理結果を直接返せない • 結果を保持させる変数をループの外側で定義する必要がある とはいうものの、インデックスを直接扱う場合は少ない インデックスを不使用なら、ループ制御はやりたくない

Slide 13

Slide 13 text

for-each文 (拡張for文) • ループの制御は不要 • Iterableインタフェースを実装したクラスで使える • List • Set • Map.entrySet() • Map.keySet() • Map.values() • Queue, Deque

Slide 14

Slide 14 text

平均を求める – for-each文バージョン scoresの要素がscoreに代入される List scores = ...; int sum = 0; for (var score: scrores) { sum += score; } double ave = (double)sum/scores.size();

Slide 15

Slide 15 text

List scores = ...; int sum = 0; for (var score: scrores) { sum += score; } double ave = (double)sum/scores.size(); 平均を求める – for-each文バージョン 変数sumが書き換え可能 ループで使用するためfinalにはできない 変数の再代入を防ぐには…

Slide 16

Slide 16 text

Stream API • ループで行う処理をラムダ式で記述 • ループではなく、データに対する処理を中心に考える • 値を返すことができる • ストリームのソースとしてコレクションを使用可能 • 慣れるまで、ちょっとたいへんかも • 慣れてしまえば、とっても便利

Slide 17

Slide 17 text

final int sum = scores.stream() .collect(Collectors.summingInt(x -> x)); final double ave = (double)sum/scores.size(); 平均を求める – Stream APIバージョン final int sum = scores.stream() .mapToInt(x -> x) .sum(); final double ave = (double)sum/scores.size(); final double ave = scores.stream() .collect(Collectors.averagingDouble(x -> x)); Ex 1. Ex 2. Ex 3.

Slide 18

Slide 18 text

record Price(ProductID id, int amount) {} Price maxPrice = prices.get(0); for (int i = 1; i < prices.size(); i++) { var price = prices.get(i); if (price.amount() > maxPrice.amount()) { maxPrice = price; } } 例 最大を見つける – for文バージョン pricesに要素がない場合 ArrayIndexOutOfBoundsException発生

Slide 19

Slide 19 text

例 最大を見つける – Stream APIバージョン pricesに要素がない場合のために Optionalクラスが使用される 比較を行うComparatorインタフェースを ラムダ式で記述 record Price(ProductID id, int amount) {} final Optional maxPrice = prices.stream() .collect(Collectors.maxBy( (p1, p2) -> p1.amount() - p2.amount()));

Slide 20

Slide 20 text

• 顧客情報を保持するコレクションから東京の顧客を抽出 record Customer(CustomerID id, String name, String prefecture) {} List tokyoCustomers = new ArrayList<>(); for (int i = 0; i < customers.size(); i++) { var customer = customers.get(i); if (customer.prefecture().equals("Tokyo")) { tokyoCustomers.add(customer); } } 例 条件に合致した要素を抽出 – for文バージョン

Slide 21

Slide 21 text

record Customer(CustomerID id, String name, String prefecture) {} List tokyoCustomers = customers.stream() .filter(c -> c.prefecture().equals("Tokyo")) .toList(); 例 条件に合致した要素を抽出 – Stream APIバージョン for文バージョンの If文と同じ条件式

Slide 22

Slide 22 text

コレクションに対するループのまとめ for文 for-each文 Stream API ループ制御 必要 不要 不要 インデックス 〇 × × ループ外の変数アクセス 〇 〇 △ final変数 orフィールド 値を返す × × 〇

Slide 23

Slide 23 text

コレクションに対するループのまとめ • Stream APIで記述できるのであれば、Stream APIを使う • インデックスを使う必要があれば、for文を使う • for-each文を使う場面はほぼない • for-each文で記述できるコードのほとんどが Stream APIで記述できる

Slide 24

Slide 24 text

特定用途に使われるコレクション

Slide 25

Slide 25 text

特定用途に使われるコレクション • キュー Queue • 両端キュー Deque (Double Ended Queue) • 発音はデック

Slide 26

Slide 26 text

Queue • First In, First Out • 最初に入れた要素が、最初に取りだされる • 先入れ、先出し A

Slide 27

Slide 27 text

Queue • First In, First Out • 最初に入れた要素が、最初に取りだされる • 先入れ、先出し A B

Slide 28

Slide 28 text

Queue • First In, First Out • 最初に入れた要素が、最初に取りだされる • 先入れ、先出し A B C

Slide 29

Slide 29 text

Queue • First In, First Out • 最初に入れた要素が、最初に取りだされる • 先入れ、先出し B A C D

Slide 30

Slide 30 text

Queue • First In, First Out • 最初に入れた要素が、最初に取りだされる • 先入れ、先出し C B D A

Slide 31

Slide 31 text

Queue • First In, First Out • 最初に入れた要素が、最初に取りだされる • 先入れ、先出し D C B A

Slide 32

Slide 32 text

Queue • First In, First Out • 最初に入れた要素が、最初に取りだされる • 先入れ、先出し • データを作る側と、使う側を結びつける • Publisher-Subscriberパターン • イベント駆動アーキテクチャ D C B A

Slide 33

Slide 33 text

Queueの使用例 - GUI マウスクリック キー入力 再描画 GUIで発生したイベントを キューに入れる MouseEventHandler KeyEventHandler RepaintManager イベントに登録された ハンドラーを実行 • マルチスレッドでのデータ間のやり取り • Blocking Queue

Slide 34

Slide 34 text

Deque • First In, Last Out • 最初に入れた要素が、最後に取りだされる • 先入れ、後出し • スタック A

Slide 35

Slide 35 text

Deque • First In, Last Out • 最初に入れた要素が、最後に取りだされる • 先入れ、後出し • スタック A B

Slide 36

Slide 36 text

Deque • First In, Last Out • 最初に入れた要素が、最後に取りだされる • 先入れ、後出し • スタック A B C

Slide 37

Slide 37 text

Deque • First In, Last Out • 最初に入れた要素が、最後に取りだされる • 先入れ、後出し • スタック A B C

Slide 38

Slide 38 text

Deque • First In, Last Out • 最初に入れた要素が、最後に取りだされる • 先入れ、後出し • スタック A B C • 状態の保存 • 逆ポーランド記法演算 • Undo, Redo

Slide 39

Slide 39 text

Dequeの使用例 - Undo, Redo • 操作をコマンドとして表現し、スタックで管理 • Undo – スタックの最後のコマンドをキャンセル • Undoの回数制限がある場合、逆端からコマンドを削除 File Open 画像拡大 色調整 例 レタッチ処理

Slide 40

Slide 40 text

Dequeの使用例 - Undo, Redo • 操作をコマンドとして表現し、スタックで管理 • Undo – スタックの最後のコマンドをキャンセル • Undoの回数制限に達したら、逆端からコマンドを削除 File Open 画像拡大 色調整 例 レタッチ処理 Undoのためコマンドをキャンセル

Slide 41

Slide 41 text

特定用途に使われるコレクションのまとめ • コレクションの特徴が特定用途にマッチする場合がある • Mapも特定用途に使用される • 辞書, Key-Valueストア • キャッシュ, メモ化 et al. • 標準ライブラリでは提供していないコレクションもある • リングバッファなど

Slide 42

Slide 42 text

並列/並行処理でのコレクション

Slide 43

Slide 43 text

簡単なまとめ • 並列/並行処理において 複数スレッドからアクセスできるコレクションは できるだけ使わない

Slide 44

Slide 44 text

なぜ並列コレクションが必要なのか? • 複数スレッドからの同時アクセスで予期せぬ動作が発生 • 最悪の場合、コレクションが壊れる • 例 MapにおけるPut-If-Absent処理 if (map.get(key) == null) { map.put(key, value); }

Slide 45

Slide 45 text

Thread #1 Thread #2 If文 put() If文 put() nullなので put実行 この時点でもnullなので put実行しvalueを上書きしてしまう これを防ぐためには Ifブロックの前にmapをロックし 単一スレッドからのアクセスに制限する 同期化

Slide 46

Slide 46 text

改訂版 Put If Absent処理 ReentrantLock lock = new ReentrantLock(); lock.lock(); try { if (map.get(key) == null) { map.put(key, value); } } finally { lock.unlock(); } 注: Java 8でMapにputIfAbsentメソッドが追加されたので 本来はそちらを使うべき

Slide 47

Slide 47 text

同期化の問題 • 単一スレッドからのアクセスに制限するため ボトルネックになりやすい • スケールするように同期化するのは難しい • ある程度、スケールするように同期化したコレクションが 並列コレクション

Slide 48

Slide 48 text

並列コレクション • インタフェース • BlockingQueu/BlockingDeque • ConcurrentMap • ConcurrentNavigableMap • 主なクラス • ConcurrentHashMap • ConcurrentLinkedQueue/Deque • CopyOnWriteArrayList/Set

Slide 49

Slide 49 text

並列コレクションと同時アクセス数のめやす 同時アクセス数 生成後はReadのみ ほぼRead ReadもWriteも 2 ~ 3 並列コレクション Immutable Collection 並列コレクション 並列コレクション ~ 10程度 並列コレクション Immutable Collection 並列コレクション BlockingQueu/Deque 10 ~ Immutable Collection 使わない 使わない Immutable Collection: 生成後の要素の追加/変更/削除を行わないコレクション Listであれば Collections. unmodifiableList(List) 並列/並行処理ではコレクションに保持させる要素も できるかぎりRecordなどを使ってイミュータブルにする

Slide 50

Slide 50 text

並列/並行処理でのコレクションのまとめ • 並列コレクションは万能ではない • 同時アクセス数が増えたらお手上げ • 並列/並行処理ではイミュータブル性が重要 • コレクションも要素もできるかぎりイミュータブルにする • そもそも同時アクセスされるような設計は防ぐ

Slide 51

Slide 51 text

まとめ • コレクションとループは強い結びつき • ループの種類ごとの特徴を把握し、使い分ける • データ構造と用途の結びつきはいろいろ • いつでも使えるように引き出しは多く • 並列/並行処理ではデータの同時アクセスに注意 • イミュータブル性が重要

Slide 52

Slide 52 text

あつめたデータをどう扱うか Java in the Box 櫻庭 祐一