0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Java基礎ガイド - コレクションとStream API

0
Posted at

Java基礎ガイド - コレクションとStream API

Java Fundamentals Guide - Collections and Stream API

目次 / Table of Contents

  1. コレクション / Collections
  2. Stream API
  3. parallelStream (並列処理)
  4. スレッドセーフ / Thread Safety
  5. effectively final

1. コレクション / Collections

概要 / Overview

日本語:
コレクションは、複数のデータをまとめて扱うための仕組みです。配列よりも柔軟で便利な機能を提供します。

English:
Collections are a framework for handling multiple data items together. They provide more flexible and convenient features than arrays.

// 配列の場合 / Array case
String[] array = new String[3];  // サイズ固定 / Fixed size
array[0] = "apple";

// コレクションの場合 / Collection case
List<String> list = new ArrayList<>();  // サイズ可変 / Variable size
list.add("apple");
list.add("banana");

コレクションの種類 / Types of Collections

日本語:
Javaのコレクションは大きく3つに分類されます:

English:
Java collections are broadly classified into three categories:

Collection Interface
├── List (順序あり、重複OK / Ordered, duplicates allowed)
│   ├── ArrayList
│   ├── LinkedList
│   └── Vector
├── Set (順序なし、重複NG / Unordered, no duplicates)
│   ├── HashSet
│   ├── LinkedHashSet
│   └── TreeSet
└── Queue (待ち行列 / Queue)
    ├── LinkedList
    ├── PriorityQueue
    └── ArrayDeque

Map Interface (キー・バリューのペア / Key-Value pairs)
├── HashMap
├── LinkedHashMap
├── TreeMap
└── Hashtable

1.1 List - リスト

日本語:
Listは順序を保持し、重複を許可するコレクションです。最もよく使われるのはArrayListです。

English:
List is a collection that maintains order and allows duplicates. ArrayList is the most commonly used implementation.

ArrayList

List<String> list = new ArrayList<>();

// 追加 / Add
list.add("apple");
list.add("banana");
list.add("cherry");
list.add("apple");  // 重複OK / Duplicates allowed

// 取得 / Get
String first = list.get(0);  // "apple"

// サイズ / Size
int size = list.size();  // 4

// 削除 / Remove
list.remove(0);  // インデックスで削除 / Remove by index
list.remove("banana");  // 値で削除 / Remove by value

// 存在確認 / Contains check
boolean hasApple = list.contains("apple");  // true

// ループ / Loop
for (String fruit : list) {
    System.out.println(fruit);
}

特徴 / Characteristics:

日本語:

  • インデックスでアクセスが速い (O(1))
  • 途中への追加・削除は遅い (O(n))
  • 最も一般的に使われる

English:

  • Fast index-based access (O(1))
  • Slow insertion/deletion in the middle (O(n))
  • Most commonly used

LinkedList

List<String> list = new LinkedList<>();
list.add("apple");
list.add("banana");

特徴 / Characteristics:

日本語:

  • 先頭・末尾への追加・削除が速い (O(1))
  • インデックスでのアクセスは遅い (O(n))
  • 途中への頻繁な追加・削除がある場合に有利

English:

  • Fast insertion/deletion at head/tail (O(1))
  • Slow index-based access (O(n))
  • Advantageous for frequent insertions/deletions

使い分け / When to Use

// 通常はArrayList / Normally use ArrayList
List<String> normalList = new ArrayList<>();

// 先頭への追加・削除が多い場合はLinkedList
// Use LinkedList for frequent head insertions/deletions
List<String> queue = new LinkedList<>();
queue.add(0, "first");

1.2 Set - セット

日本語:
Setは重複を許可しないコレクションです。自動的に重複を除去します。

English:
Set is a collection that does not allow duplicates. It automatically removes duplicate elements.

HashSet

Set<String> set = new HashSet<>();

// 追加 / Add
set.add("apple");
set.add("banana");
set.add("apple");  // 重複は無視される / Duplicate is ignored

System.out.println(set.size());  // 2

// 存在確認 / Contains check
boolean hasApple = set.contains("apple");  // とても速い / Very fast O(1)

// ループ(順序は保証されない) / Loop (order not guaranteed)
for (String fruit : set) {
    System.out.println(fruit);
}

特徴 / Characteristics:

日本語:

  • 重複を自動的に除去
  • 追加・削除・検索が速い (O(1))
  • 順序は保証されない

English:

  • Automatically removes duplicates
  • Fast add/remove/search operations (O(1))
  • Order is not guaranteed

LinkedHashSet

Set<String> set = new LinkedHashSet<>();
set.add("cherry");
set.add("apple");
set.add("banana");

// 追加した順序で出力される / Output in insertion order
for (String fruit : set) {
    System.out.println(fruit);  // cherry, apple, banana
}

TreeSet

Set<String> set = new TreeSet<>();
set.add("cherry");
set.add("apple");
set.add("banana");

// 自動的にソートされる / Automatically sorted
for (String fruit : set) {
    System.out.println(fruit);  // apple, banana, cherry
}

特徴 / Characteristics:

日本語:

  • 自動的にソートされる
  • 追加・削除・検索は少し遅い (O(log n))

English:

  • Automatically sorted
  • Slightly slower operations (O(log n))

1.3 Map - マップ

日本語:
Mapはキーと値のペアを保存するコレクションです。キーで値を検索できます。

English:
Map is a collection that stores key-value pairs. Values can be retrieved by keys.

HashMap

Map<String, Integer> map = new HashMap<>();

// 追加 / Add
map.put("apple", 100);
map.put("banana", 200);
map.put("cherry", 150);

// 取得 / Get
Integer applePrice = map.get("apple");  // 100
Integer grapePrice = map.get("grape");  // null (存在しない / doesn't exist)

// デフォルト値付き取得 / Get with default value
Integer price = map.getOrDefault("grape", 0);  // 0

// 存在確認 / Check existence
boolean hasApple = map.containsKey("apple");  // true
boolean has100 = map.containsValue(100);  // true

// サイズ / Size
int size = map.size();  // 3

// 削除 / Remove
map.remove("banana");

// ループ / Loop through entries
for (Map.Entry<String, Integer> entry : map.entrySet()) {
    System.out.println(entry.getKey() + ": " + entry.getValue());
}

// キーだけループ / Loop through keys only
for (String key : map.keySet()) {
    System.out.println(key);
}

// 値だけループ / Loop through values only
for (Integer value : map.values()) {
    System.out.println(value);
}

特徴 / Characteristics:

日本語:

  • 追加・削除・検索が速い (O(1))
  • 順序は保証されない
  • キーは重複不可、値は重複OK

English:

  • Fast add/remove/search operations (O(1))
  • Order is not guaranteed
  • Keys must be unique, values can be duplicated

LinkedHashMap

Map<String, Integer> map = new LinkedHashMap<>();
map.put("cherry", 150);
map.put("apple", 100);
map.put("banana", 200);

// 追加した順序で出力 / Output in insertion order
for (String key : map.keySet()) {
    System.out.println(key);  // cherry, apple, banana
}

TreeMap

Map<String, Integer> map = new TreeMap<>();
map.put("cherry", 150);
map.put("apple", 100);
map.put("banana", 200);

// キーでソートされる / Sorted by key
for (String key : map.keySet()) {
    System.out.println(key);  // apple, banana, cherry
}

実践的な使用例 / Practical Examples

例1: 重複除去 / Example 1: Remove Duplicates

List<String> files = Arrays.asList("a.txt", "b.txt", "a.txt", "c.txt");

// 重複除去 / Remove duplicates
Set<String> uniqueFiles = new HashSet<>(files);
System.out.println(uniqueFiles);  // [a.txt, b.txt, c.txt]

// Listに戻す / Convert back to List
List<String> uniqueList = new ArrayList<>(uniqueFiles);

例2: カウント処理 / Example 2: Counting

List<String> words = Arrays.asList("apple", "banana", "apple", "cherry", "banana", "apple");

// 各単語の出現回数をカウント / Count occurrences of each word
Map<String, Integer> count = new HashMap<>();
for (String word : words) {
    count.put(word, count.getOrDefault(word, 0) + 1);
}

System.out.println(count);  // {apple=3, banana=2, cherry=1}

2. Stream API

概要 / Overview

日本語:
Stream APIはJava 8で導入された、コレクションのデータを処理するための機能です。従来のfor文よりも読みやすく、簡潔にデータ処理を記述できます。

English:
Stream API was introduced in Java 8 for processing collection data. It allows you to write data processing logic more readably and concisely than traditional for loops.

従来の書き方 vs Stream / Traditional vs Stream

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

// 従来のfor文: 偶数を2倍にして新しいリストを作る
// Traditional for loop: Double even numbers and create a new list
List<Integer> result = new ArrayList<>();
for (Integer num : numbers) {
    if (num % 2 == 0) {  // 偶数のみ / Even numbers only
        result.add(num * 2);
    }
}

// Stream: 同じ処理 / Stream: Same processing
List<Integer> result = numbers.stream()
    .filter(num -> num % 2 == 0)  // 偶数のみ / Even numbers only
    .map(num -> num * 2)           // 2倍にする / Double
    .collect(Collectors.toList()); // リストに変換 / Convert to list

Streamの作成 / Creating Streams

// 1. Collectionから / From Collection
List<String> list = Arrays.asList("a", "b", "c");
Stream<String> stream1 = list.stream();

// 2. 配列から / From array
String[] array = {"a", "b", "c"};
Stream<String> stream2 = Arrays.stream(array);

// 3. 直接生成 / Direct creation
Stream<String> stream3 = Stream.of("a", "b", "c");

// 4. 範囲指定 / Range
IntStream stream4 = IntStream.range(1, 10);  // 1から9まで / 1 to 9

中間操作 / Intermediate Operations

日本語:
中間操作は新しいStreamを返し、複数つなげることができます。

English:
Intermediate operations return a new Stream and can be chained together.

filter() - フィルタリング

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

List<Integer> evens = numbers.stream()
    .filter(n -> n % 2 == 0)  // 偶数だけ / Even numbers only
    .collect(Collectors.toList());
// 結果 / Result: [2, 4]

map() - 変換

List<String> names = Arrays.asList("alice", "bob", "charlie");

// 大文字に変換 / Convert to uppercase
List<String> upperNames = names.stream()
    .map(name -> name.toUpperCase())
    .collect(Collectors.toList());
// 結果 / Result: ["ALICE", "BOB", "CHARLIE"]

// 別の型に変換 / Convert to different type
List<Integer> nameLengths = names.stream()
    .map(name -> name.length())
    .collect(Collectors.toList());
// 結果 / Result: [5, 3, 7]

sorted() - ソート

List<Integer> numbers = Arrays.asList(5, 2, 8, 1, 9);

// 昇順 / Ascending order
List<Integer> sorted = numbers.stream()
    .sorted()
    .collect(Collectors.toList());
// 結果 / Result: [1, 2, 5, 8, 9]

// 降順 / Descending order
List<Integer> reverseSorted = numbers.stream()
    .sorted(Comparator.reverseOrder())
    .collect(Collectors.toList());
// 結果 / Result: [9, 8, 5, 2, 1]

distinct() - 重複除去

List<Integer> numbers = Arrays.asList(1, 2, 2, 3, 3, 3, 4);

List<Integer> unique = numbers.stream()
    .distinct()
    .collect(Collectors.toList());
// 結果 / Result: [1, 2, 3, 4]

limit() / skip() - 数の制限/スキップ

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

// 最初の3つ / First 3 elements
List<Integer> first3 = numbers.stream()
    .limit(3)
    .collect(Collectors.toList());
// 結果 / Result: [1, 2, 3]

// 最初の3つをスキップ / Skip first 3 elements
List<Integer> skip3 = numbers.stream()
    .skip(3)
    .collect(Collectors.toList());
// 結果 / Result: [4, 5, 6, 7, 8, 9, 10]

終端操作 / Terminal Operations

日本語:
終端操作はStreamを消費して結果を返します。終端操作は1度しか実行できません。

English:
Terminal operations consume the Stream and return a result. They can only be executed once.

collect() - コレクションに変換

List<String> names = Arrays.asList("alice", "bob", "charlie");

// Listに変換 / Convert to List
List<String> list = names.stream()
    .filter(name -> name.length() > 3)
    .collect(Collectors.toList());

// Setに変換 / Convert to Set
Set<String> set = names.stream()
    .collect(Collectors.toSet());

// 文字列結合 / Join strings
String joined = names.stream()
    .collect(Collectors.joining(", "));
// 結果 / Result: "alice, bob, charlie"

forEach() - 各要素に処理を実行

List<String> names = Arrays.asList("alice", "bob", "charlie");

names.stream()
    .forEach(name -> System.out.println(name));
// 各名前を出力 / Print each name

count() - カウント

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

long count = numbers.stream()
    .filter(n -> n > 3)
    .count();
// 結果 / Result: 2 (4と5 / 4 and 5)

anyMatch() / allMatch() / noneMatch() - 条件判定

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

// 偶数が1つでもある? / Is there any even number?
boolean hasEven = numbers.stream()
    .anyMatch(n -> n % 2 == 0);
// 結果 / Result: true

// 全て正の数? / Are all numbers positive?
boolean allPositive = numbers.stream()
    .allMatch(n -> n > 0);
// 結果 / Result: true

// 負の数が1つもない? / Are there no negative numbers?
boolean noNegative = numbers.stream()
    .noneMatch(n -> n < 0);
// 結果 / Result: true

findFirst() / findAny() - 要素を取得

List<String> names = Arrays.asList("alice", "bob", "charlie");

Optional<String> first = names.stream()
    .filter(name -> name.startsWith("b"))
    .findFirst();
// 結果 / Result: Optional["bob"]

if (first.isPresent()) {
    System.out.println(first.get());  // "bob"
}

reduce() - 集約

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

// 合計 / Sum
int sum = numbers.stream()
    .reduce(0, (a, b) -> a + b);
// 結果 / Result: 15

// 別の書き方 / Alternative
int sum2 = numbers.stream()
    .reduce(0, Integer::sum);

// 最大値 / Maximum value
Optional<Integer> max = numbers.stream()
    .reduce((a, b) -> a > b ? a : b);
// 結果 / Result: Optional[5]

複数操作の組み合わせ / Combining Multiple Operations

List<String> names = Arrays.asList("alice", "bob", "charlie", "dave", "eve");

// 4文字以上の名前を大文字にして、カンマ区切りで結合
// Names with 4+ characters, uppercase, joined with commas
String result = names.stream()
    .filter(name -> name.length() >= 4)     // 4文字以上 / 4+ chars
    .map(name -> name.toUpperCase())         // 大文字に変換 / To uppercase
    .sorted()                                 // ソート / Sort
    .collect(Collectors.joining(", "));      // 結合 / Join
// 結果 / Result: "ALICE, CHARLIE, DAVE"

実践例 / Practical Examples

ファイルリストの処理 / Processing File Lists

List<String> files = Arrays.asList(
    "document.pdf", 
    "image.jpg", 
    "script.js", 
    "data.json",
    "photo.jpg"
);

// JPGファイルだけ抽出 / Extract only JPG files
List<String> jpgFiles = files.stream()
    .filter(file -> file.endsWith(".jpg"))
    .collect(Collectors.toList());
// 結果 / Result: ["image.jpg", "photo.jpg"]

オブジェクトのリストを処理 / Processing Object Lists

class User {
    String name;
    int age;
    
    User(String name, int age) {
        this.name = name;
        this.age = age;
    }
    
    public String getName() { return name; }
    public int getAge() { return age; }
}

List<User> users = Arrays.asList(
    new User("Alice", 25),
    new User("Bob", 30),
    new User("Charlie", 20)
);

// 25歳以上のユーザーの名前リスト
// List of names of users aged 25 or older
List<String> names = users.stream()
    .filter(user -> user.getAge() >= 25)
    .map(User::getName)
    .collect(Collectors.toList());
// 結果 / Result: ["Alice", "Bob"]

// 平均年齢 / Average age
double avgAge = users.stream()
    .mapToInt(User::getAge)
    .average()
    .orElse(0.0);
// 結果 / Result: 25.0

重要な注意点 / Important Notes

日本語:

  1. Streamは1度しか使えない - 終端操作後は再利用不可
  2. 元のコレクションは変更されない - Streamは新しいコレクションを作成
  3. 遅延評価 - 終端操作が実行されるまで中間操作は実行されない

English:

  1. Streams can only be used once - Cannot be reused after terminal operation
  2. Original collection is not modified - Stream creates a new collection
  3. Lazy evaluation - Intermediate operations aren't executed until terminal operation
// Streamは1度しか使えない / Stream can only be used once
Stream<String> stream = names.stream();
stream.forEach(System.out::println);  // OK
stream.forEach(System.out::println);  // エラー! / Error!

// 元のコレクションは変更されない / Original collection is not modified
List<Integer> numbers = Arrays.asList(1, 2, 3);
numbers.stream()
    .map(n -> n * 2)
    .collect(Collectors.toList());
System.out.println(numbers);  // [1, 2, 3] のまま / Still [1, 2, 3]

3. parallelStream (並列処理)

概要 / Overview

日本語:
parallelStreamは、Stream処理を複数のスレッドで自動的に並列実行することで、大量のデータ処理を高速化できます。

English:
parallelStream automatically executes Stream processing in parallel using multiple threads, which can speed up processing of large amounts of data.

基本的な使い方 / Basic Usage

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

// 通常のstream / Regular stream
numbers.stream()
    .map(n -> n * 2)
    .forEach(System.out::println);

// parallelStream
numbers.parallelStream()
    .map(n -> n * 2)
    .forEach(System.out::println);

内部の仕組み / How It Works

日本語:
parallelStreamはForkJoinPoolを使用します。デフォルトでは利用可能なCPUコア数に基づいてスレッド数が決定されます。

English:
parallelStream uses ForkJoinPool. By default, the number of threads is determined based on the number of available CPU cores.

// 利用可能なプロセッサ数 / Number of available processors
int cores = Runtime.getRuntime().availableProcessors();
System.out.println("CPU Cores: " + cores);

適切な使用場面 / When to Use

日本語:
parallelStreamが効果的なケース:

  • データ量が十分に大きい(目安:数千件以上)
  • 各要素の処理が独立している
  • 各要素の処理に時間がかかる(計算集約的な処理)

English:
Effective use cases for parallelStream:

  • Sufficiently large data set (guideline: thousands of items or more)
  • Processing of each element is independent
  • Processing of each element is time-consuming (computation-intensive)
// 効果的な例: 重い計算処理
// Effective example: Heavy computation
List<String> files = getFileList(); // 1000個のファイル / 1000 files

List<ProcessedData> results = files.parallelStream()
    .map(file -> processHeavyComputation(file))
    .collect(Collectors.toList());

注意点 / Important Considerations

1. 処理順序が保証されない / Order is Not Guaranteed

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

numbers.parallelStream()
    .forEach(System.out::println); // 順序がバラバラ / Random order

2. スレッドセーフでない操作は危険 / Thread-Unsafe Operations are Dangerous

// NG例: 複数スレッドから同時にaddされる
// Bad example: Multiple threads adding simultaneously
List<Integer> result = new ArrayList<>();
numbers.parallelStream()
    .forEach(n -> result.add(n * 2)); // スレッドセーフでない! / Not thread-safe!

// OK例 / Good example
List<Integer> result = numbers.parallelStream()
    .map(n -> n * 2)
    .collect(Collectors.toList()); // スレッドセーフ / Thread-safe

3. オーバーヘッドの考慮 / Consider Overhead

日本語:
データ量が少ない場合、並列化のオーバーヘッドで逆に遅くなることがあります。

English:
For small data sets, parallelization overhead can actually make processing slower.

// 100件程度のデータでparallelStreamは逆効果の可能性
// parallelStream may be counterproductive for ~100 items
smallList.stream() // 通常のstreamの方が速い場合も / Regular stream may be faster
    .filter(...)
    .collect(Collectors.toList());

実践的な例 / Practical Example

// 大量ファイルの並列処理
// Parallel processing of many files
List<String> fileKeys = getFileKeys(); // 1000個のキー / 1000 keys

List<FileData> results = fileKeys.parallelStream()
    .map(key -> {
        try {
            return fileService.getFile(key);
        } catch (Exception e) {
            logger.error("Failed to get file: " + key, e);
            return null;
        }
    })
    .filter(Objects::nonNull)
    .collect(Collectors.toList());

パフォーマンス測定 / Performance Measurement

// stream vs parallelStream の比較
// Comparison of stream vs parallelStream

long start = System.currentTimeMillis();
List<Integer> result1 = largeList.stream()
    .map(n -> heavyComputation(n))
    .collect(Collectors.toList());
long elapsed1 = System.currentTimeMillis() - start;
System.out.println("stream: " + elapsed1 + "ms");

start = System.currentTimeMillis();
List<Integer> result2 = largeList.parallelStream()
    .map(n -> heavyComputation(n))
    .collect(Collectors.toList());
long elapsed2 = System.currentTimeMillis() - start;
System.out.println("parallelStream: " + elapsed2 + "ms");

4. スレッドセーフ / Thread Safety

概要 / Overview

日本語:
スレッドセーフとは、複数のスレッドから同時にアクセスされても、正しく動作することを保証する性質のことです。

English:
Thread safety is the property that guarantees correct behavior when accessed simultaneously by multiple threads.


問題が起きる例 / Problematic Example

public class Counter {
    private int count = 0;
    
    public void increment() {
        count++;  // スレッドセーフでない! / Not thread-safe!
    }
    
    public int getCount() {
        return count;
    }
}

// 使用例 / Usage example
Counter counter = new Counter();

// 複数スレッドから同時に呼び出す / Called by multiple threads
List<Integer> numbers = IntStream.range(0, 1000)
    .boxed()
    .collect(Collectors.toList());
numbers.parallelStream().forEach(n -> counter.increment());

System.out.println(counter.getCount()); 
// 期待値 / Expected: 1000
// 実際 / Actual: 950や980など、毎回バラバラ / Random values like 950, 980

なぜ問題が起きるのか / Why Problems Occur

日本語:
count++は実際には3つの操作に分かれています:

English:
count++ is actually split into three operations:

1. メモリからcountの値を読み取る / Read count value from memory (read)
2. 値に1を足す / Add 1 to the value (modify)
3. メモリに書き戻す / Write back to memory (write)

日本語:
複数のスレッドが同時に実行すると:

English:
When multiple threads execute simultaneously:

スレッドA / Thread A: count(0)を読む / Read count(0)
スレッドB / Thread B: count(0)を読む  ← まだ0のまま! / Still 0!
スレッドA / Thread A: 0+1=1を書き込む / Write 0+1=1
スレッドB / Thread B: 0+1=1を書き込む ← 結果が1のまま / Result stays at 1
                       (本来は2になるべき / Should be 2)

よくある問題パターン / Common Problem Patterns

1. ArrayListへの同時追加 / Simultaneous Addition to ArrayList

// NG例 / Bad example
List<String> results = new ArrayList<>();
items.parallelStream().forEach(item -> {
    results.add(process(item));  // 複数スレッドから同時add / Concurrent add
});
// ArrayListの内部状態が壊れる可能性 / ArrayList's internal state may be corrupted

2. HashMapへの同時書き込み / Concurrent Writes to HashMap

// NG例 / Bad example
Map<String, Integer> map = new HashMap<>();
items.parallelStream().forEach(item -> {
    map.put(item.getKey(), item.getValue());  // 危険! / Dangerous!
});

3. 共有変数の更新 / Updating Shared Variables

// NG例 / Bad example
int sum = 0;  // 実質的にfinalでないとコンパイルエラー
              // Compile error if not effectively final
numbers.parallelStream().forEach(n -> {
    sum += n;  // コンパイルエラー / Compile error
});

解決方法 / Solutions

方法1: スレッドセーフなコレクションを使う / Use Thread-Safe Collections

// OK例: CopyOnWriteArrayList / Good example
List<String> results = new CopyOnWriteArrayList<>();
items.parallelStream().forEach(item -> {
    results.add(process(item));
});

// OK例: ConcurrentHashMap / Good example
Map<String, Integer> map = new ConcurrentHashMap<>();
items.parallelStream().forEach(item -> {
    map.put(item.getKey(), item.getValue());
});

方法2: Collectorsを使う(推奨)/ Use Collectors (Recommended)

// 最も推奨される方法 / Most recommended approach
List<String> results = items.parallelStream()
    .map(item -> process(item))
    .collect(Collectors.toList());  // 内部でスレッドセーフに処理
                                     // Processed thread-safely internally

Map<String, Integer> map = items.parallelStream()
    .collect(Collectors.toMap(
        Item::getKey,
        Item::getValue
    ));

方法3: synchronizedを使う / Use synchronized

public class Counter {
    private int count = 0;
    
    public synchronized void increment() {
        count++;  // 同時に1スレッドしか実行できない
                  // Only one thread can execute at a time
    }
    
    public synchronized int getCount() {
        return count;
    }
}

方法4: AtomicInteger等を使う / Use AtomicInteger

import java.util.concurrent.atomic.AtomicInteger;

AtomicInteger counter = new AtomicInteger(0);

numbers.parallelStream().forEach(n -> {
    counter.incrementAndGet();  // スレッドセーフ / Thread-safe
});

System.out.println(counter.get());  // 正確に1000 / Exactly 1000

スレッドセーフなコレクション一覧 / Thread-Safe Collections

日本語:

通常のコレクション スレッドセーフな代替
ArrayList CopyOnWriteArrayList
HashSet ConcurrentHashMap.newKeySet()
HashMap ConcurrentHashMap
TreeMap ConcurrentSkipListMap

English:

Regular Collection Thread-Safe Alternative
ArrayList CopyOnWriteArrayList
HashSet ConcurrentHashMap.newKeySet()
HashMap ConcurrentHashMap
TreeMap ConcurrentSkipListMap

5. effectively final

概要 / Overview

日本語:
Streamのラムダ式内で使う外部変数は、effectively final(実質的にfinal)である必要があります。

English:
External variables used inside lambda expressions in Streams must be effectively final (essentially final).


NG例: 変数を変更しようとする / Bad Example: Trying to Modify Variables

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

int sum = 0;  // effectively finalではない / Not effectively final

numbers.stream().forEach(n -> {
    sum += n;  // コンパイルエラー! / Compile error!
    // エラーメッセージ / Error message:
    // Local variable sum defined in an enclosing scope 
    // must be final or effectively final
});

OK例: 変更しない変数 / Good Example: Unmodified Variables

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

int threshold = 3;  // effectively final (値を変更していない)
                    // effectively final (value is not changed)

List<Integer> filtered = numbers.stream()
    .filter(n -> n > threshold)  // OK!
    .collect(Collectors.toList());

effectively finalとは / What is effectively final?

日本語:
effectively final = finalキーワードがなくても、実質的にfinalと同じ(値が変更されない)変数のこと

English:
effectively final = A variable that is essentially final (its value is not changed) even without the final keyword

// これはeffectively final / This is effectively final
int x = 10;
numbers.stream().forEach(n -> System.out.println(n + x));  // OK

// これはeffectively finalではない / This is NOT effectively final
int y = 10;
y = 20;  // 値を変更している / Value is changed
numbers.stream().forEach(n -> System.out.println(n + y));  // エラー! / Error!

なぜこの制約があるのか / Why This Restriction Exists

日本語:
ラムダ式は内部的に別のクラスとして実装され、外部変数のコピーを保持します。変更可能にすると、元の変数とコピーが同期しなくなり、予期しない動作を引き起こすためです。

English:
Lambda expressions are internally implemented as separate classes and hold copies of external variables. Allowing modifications would cause the original variable and its copy to go out of sync, leading to unexpected behavior.

int counter = 0;

// もしこれが許可されていたら...
// If this were allowed...
numbers.stream().forEach(n -> {
    counter++;  // これはコピーを変更している
                // This modifies the copy
    // 元のcounterは変わらない = 混乱の元
    // Original counter doesn't change = source of confusion
});

ラムダ式の内部実装イメージ / Internal Implementation of Lambda

List<Integer> numbers = Arrays.asList(1, 2, 3);
int threshold = 5;

numbers.stream()
    .filter(n -> n > threshold)
    .collect(Collectors.toList());

日本語:
内部的には、このようなクラスが生成されるイメージです:

English:
Internally, it's similar to generating a class like this:

// コンパイラが自動生成(イメージ)
// Auto-generated by compiler (conceptual)
class Lambda$1 implements Predicate<Integer> {
    private final int threshold;  // 外部変数のコピー / Copy of external variable
    
    Lambda$1(int threshold) {
        this.threshold = threshold;  // コンストラクタで値をコピー
                                      // Copy value in constructor
    }
    
    @Override
    public boolean test(Integer n) {
        return n > this.threshold;
    }
}

解決方法 / Solutions

方法1: reduce()を使う(推奨)/ Use reduce() (Recommended)

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

int sum = numbers.stream()
    .reduce(0, (a, b) -> a + b);  // 合計を計算 / Calculate sum
System.out.println(sum);  // 15

方法2: AtomicIntegerを使う / Use AtomicInteger

import java.util.concurrent.atomic.AtomicInteger;

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

AtomicInteger sum = new AtomicInteger(0);

numbers.stream().forEach(n -> {
    sum.addAndGet(n);  // OK! (オブジェクト自体は変更していない)
                       // OK! (Not modifying the object itself)
});

System.out.println(sum.get());  // 15

方法3: 配列を使う(あまり推奨しない)/ Use Array (Not Recommended)

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

int[] sum = {0};  // 配列自体はfinalだが、中身は変更可能
                  // Array itself is final, but contents are mutable

numbers.stream().forEach(n -> {
    sum[0] += n;  // OK (配列の参照は変更していない)
                  // OK (Not modifying array reference)
});

System.out.println(sum[0]);  // 15

effectively final vs イミュータブル / effectively final vs Immutable

effectively final(変数への再代入禁止)

// OK: 変数の再代入なし / No reassignment
final int x = 10;
// x = 20;  // これができない / Cannot do this

List<String> list = new ArrayList<>();  // listはeffectively final
                                         // list is effectively final
numbers.stream().forEach(n -> {
    list.add(String.valueOf(n));  // OK! listの再代入ではない
                                   // OK! Not reassigning list
});

イミュータブル(オブジェクトの内容変更禁止)

String str = "hello";  // Stringはイミュータブル / String is immutable
str.toUpperCase();  // 新しいStringオブジェクトを返す
                    // Returns new String object
System.out.println(str);  // "hello"のまま / Still "hello"

まとめ / Summary

日本語:

概念 制約の対象
effectively final 変数への再代入 x = 20;ができない
イミュータブル オブジェクトの内容 str.setXxx()のようなメソッドがない
  • ラムダ式で要求されるのはeffectively final(変数の再代入禁止)
  • 必ずしもイミュータブルなオブジェクトを使う必要はない
  • parallelStreamを使う場合は、スレッドセーフ(≒イミュータブルまたは同期化)も必要

English:

Concept Restriction Target Example
effectively final Variable reassignment Cannot do x = 20;
Immutable Object contents No methods like str.setXxx()
  • Lambda expressions require effectively final (no variable reassignment)
  • Don't necessarily need to use immutable objects
  • When using parallelStream, thread safety (≈ immutable or synchronized) is also required

実践的なまとめ / Practical Summary

データ処理のベストプラクティス / Best Practices for Data Processing

// 1. 通常の処理: Streamを使う
// Normal processing: Use Stream
List<String> filtered = list.stream()
    .filter(s -> s.length() > 5)
    .map(String::toUpperCase)
    .collect(Collectors.toList());

// 2. 大量データ: parallelStreamを検討
// Large data: Consider parallelStream
List<Result> results = largeList.parallelStream()
    .map(item -> heavyProcess(item))
    .collect(Collectors.toList());  // スレッドセーフ / Thread-safe

// 3. 変数の変更が必要: reduce()やcollect()を使う
// Need to modify variables: Use reduce() or collect()
int sum = numbers.stream()
    .reduce(0, Integer::sum);

// 4. グループ化・集計: Collectorsを活用
// Grouping/Aggregation: Utilize Collectors
Map<String, List<User>> grouped = users.stream()
    .collect(Collectors.groupingBy(User::getDepartment));

// 5. 重複除去: Setを活用
// Remove duplicates: Use Set
Set<String> unique = new HashSet<>(listWithDuplicates);

避けるべきパターン / Patterns to Avoid

// ❌ NG: スレッドセーフでないコレクションへの並列追加
// Bad: Parallel additions to non-thread-safe collections
List<String> result = new ArrayList<>();
items.parallelStream().forEach(item -> result.add(item));

// ❌ NG: ラムダ式内での外部変数の変更
// Bad: Modifying external variables in lambda
int count = 0;
items.stream().forEach(item -> count++);  // コンパイルエラー / Compile error

// ❌ NG: 小さいデータでparallelStreamを使う
// Bad: Using parallelStream for small data
smallList.parallelStream()  // オーバーヘッドの方が大きい
         .map(...)           // Overhead is larger
         .collect(...);

// ✅ OK: collect()を使う / Use collect()
List<String> result = items.parallelStream()
    .map(item -> process(item))
    .collect(Collectors.toList());

// ✅ OK: reduce()を使う / Use reduce()
int count = items.stream()
    .mapToInt(item -> 1)
    .sum();

// ✅ OK: 大きいデータでのみparallelStream / parallelStream only for large data
if (list.size() > 1000) {
    list.parallelStream()...
} else {
    list.stream()...
}

参考資料 / References

日本語:

  • Java公式ドキュメント: Stream API
  • Java Concurrency in Practice
  • Effective Java (Third Edition) - Joshua Bloch

English:

  • Java Official Documentation: Stream API
  • Java Concurrency in Practice
  • Effective Java (Third Edition) - Joshua Bloch

まとめ / Conclusion

日本語:
このガイドでは、Javaのコレクション、Stream API、並列処理、スレッドセーフ、effectively finalについて解説しました。これらの概念を理解し、適切に使い分けることで、効率的で安全なコードを書くことができます。

English:
This guide covered Java Collections, Stream API, parallel processing, thread safety, and effectively final. By understanding these concepts and using them appropriately, you can write efficient and safe code.


最終更新 / Last Updated: 2024
対象バージョン / Target Version: Java 8+

0
0
0

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
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?