初心者からプロへ!Java ArrayListマスターガイド:現場で差がつくメソッド活用術

こんにちは。ゆうせいです。

新人エンジニアの皆さんが現場で最も頻繁に目にすると言っても過言ではない、ArrayList。 「なんとなく要素を追加できる便利な配列」だと思っていませんか。 実は、その内部構造やメソッドの特性を理解していないと、思わぬバグやパフォーマンスの低下を招くことがあります。 今日は、企業研修の講師として、皆さんにArrayListの真髄を伝授しますね!

まずは、本研修で学んでいく全体像を確認しましょう。

章構成

  1. 第1章:要素の追加と内部挙動(addメソッドの魔法とコスト)
  2. 第2章:取得と更新の作法(getとsetを使いこなす)
  3. 第3章:削除の罠(removeのオーバーロードと要素のシフト)
  4. 第4章:検索と状態確認(contains、indexOf、isEmptyの裏側)
  5. 第5章:一括操作とメモリ管理(clear、toArrayとガベージコレクション)
  6. 第6章:反復処理の正解(拡張for文、Iterator、そして恐怖の例外)
  7. 第7章:設計の極意(Listインターフェースの活用とLinkedListとの使い分け)
  8. 第8章:モダンJavaへの招待(Stream APIとの連携)

それでは、記念すべき第1章をスタートしましょう。

第1章:要素の追加と内部挙動(addメソッドの魔法とコスト)

概要

ArrayListの基本中の基本である要素の追加について学びます。末尾に追加する場合と、場所を指定して割り込ませる場合では、コンピュータの中で起きている「手間」が全く異なります。この違いを意識できるようになることが、最初のステップです。

対象メソッド

  • add(E e)
  • add(int index, E element)

サンプルコード

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

public class Main {
    public static void main(String[] args) {
        List<String> trainees = new ArrayList<>();
        
        trainees.add("佐藤");
        trainees.add("鈴木");
        trainees.add(1, "高橋");
        
        System.out.println(trainees);
    }
}

実行結果

[佐藤, 高橋, 鈴木]

逐次解説

  1. List<String> trainees = new ArrayList<>(); String型の要素を格納するための空のArrayListを生成します。変数の方はListインターフェース型で宣言するのがJavaの定石です。
  2. trainees.add("佐藤"); リストの末尾(現在は一番先頭)に「佐藤」を追加します。
  3. trainees.add("鈴木"); 現在の末尾である「佐藤」の後ろに「鈴木」を追加します。
  4. trainees.add(1, "高橋"); インデックス1番、つまり「佐藤」と「鈴木」の間に「高橋」を割り込ませます。
  5. System.out.println(trainees); リストの中身を全件表示します。

イメージ

ArrayListは、内部的には「サイズが自動で変わる配列」です。

  • add("佐藤") [ 佐藤 ] (空きスペース)
  • add("鈴木") [ 佐藤 | 鈴木 ] (空きスペース)
  • add(1, "高橋") ここがポイントです!インデックス1に「高橋」を入れるために、もともと1番にいた「鈴木」を右側に1つズラす作業(シフト)が発生します。
    1. [ 佐藤 | (空き) | 鈴木 ] ← 鈴木を右へ移動
    2. [ 佐藤 | 高橋 | 鈴木 ] ← 空いた場所に高橋を入れる

よくある誤解

「ArrayListは無限に要素が入る箱だ」と思っていませんか。 実際には内部に普通の配列を持っており、足りなくなると「一回り大きな配列を新しく作って、中身を全部コピーする」という力技を行っています。自動で増えるのは便利ですが、裏側では必死にコピー作業が行われていることを忘れないでください。

実務での重要性

場所を指定する add(index, element) は、リストが長くなればなるほど、後ろにある要素すべてを動かす必要があるため、処理が重くなります。大量のデータを扱う際に、頻繁に途中に割り込ませる処理を書くと、システムの動作が目に見えて遅くなる原因になります。

演習問題

問1:要素が1000個あるArrayListの先頭(インデックス0)に新しい要素を追加した場合、内部で移動(シフト)が必要な要素は何個でしょうか。 問2:ArrayListの末尾に要素を追加する処理と、指定した位置に要素を追加する処理、どちらが計算負荷が低い(速い)と言えるでしょうか。その理由も答えてください。


いかがでしたか。 次は、これらの要素を取り出したり書き換えたりする方法を詳しく見ていきます。

第2章では、リストに入れたデータを取り出したり、中身を書き換えたりする操作を学んでいきましょう。 「入れたものを使う」という、プログラムの最も基本となる部分ですね!

第2章:取得と更新の作法(getとsetを使いこなす)

概要

配列と同じように、ArrayListもインデックス(添え字)を指定して値を操作します。ここで重要なのは、値を取得する get と、値を上書きする set の違いを明確に区別することです。

対象メソッド

  • get(int index)
  • set(int index, E element)

サンプルコード

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

public class Main {
    public static void main(String[] args) {
        List<String> trainees = new ArrayList<>();
        trainees.add("佐藤");
        trainees.add("高橋");

        String name = trainees.get(0);
        System.out.println("0番目は: " + name);

        String oldName = trainees.set(1, "田中");
        System.out.println("変更前: " + oldName);
        System.out.println("変更後のリスト: " + trainees);
    }
}

実行結果

0番目は: 佐藤 変更前: 高橋 変更後のリスト: [佐藤, 田中]

逐次解説

  1. List<String> trainees = new ArrayList<>(); String型のリストを準備します。
  2. trainees.add("佐藤"); trainees.add("高橋"); 要素を2つ追加しました。この時点でインデックス0は佐藤、1は高橋です。
  3. String name = trainees.get(0); インデックス0番の要素(佐藤)を「参照」して変数に代入します。リストの中身は変わりません。
  4. String oldName = trainees.set(1, "田中"); インデックス1番の要素を「田中」に「上書き」します。
  5. System.out.println("変更前: " + oldName); setメソッドは、上書きされる前の古い値(高橋)を戻り値として返してくれます。これ、意外と便利なんですよ!
  6. System.out.println("変更後のリスト: " + trainees); リストを表示すると、高橋が消えて田中に変わっていることが確認できます。

イメージ

イメージとしては、棚(リスト)にある箱を想像してください。

  • get(0) 0番の棚にある箱の中身を「のぞき見る」だけ。箱はそのまま棚に残ります。
  • set(1, "田中") 1番の棚にある箱を取り出して捨て、代わりに「田中」という新しい箱を置く作業です。

よくある誤解

「setメソッドを使うと、要素が間に割り込んで増える」と勘違いする人がいますが、それは間違いです! 第1章で学んだ add(index, element) は要素を「押し広げて追加」しますが、set(index, element) は「元の値を消して入れ替える」だけです。したがって、リストの全体のサイズ(要素数)は変わりません。

実務での重要性

getメソッドは非常に高速です。ArrayListは内部が配列なので、何番目であっても一瞬でアクセスできます。 一方で、存在しないインデックス(例えば要素が2つしかないのに 10番目など)を指定すると、IndexOutOfBoundsException という実行時エラーが発生してプログラムが止まってしまいます。これを防ぐために、後述する size() メソッドでのチェックが欠かせません。

演習問題

問1:要素が「佐藤、高橋、鈴木」の順で並んでいるリストに対して、set(1, "伊藤") を実行した後のリストの内容を答えてください。 問2:getメソッドとsetメソッドのうち、実行した後にリストの size() (要素数)が変わる可能性があるものはどちらでしょうか。

第3章では、リストから要素を消し去る「削除」について深掘りしましょう。 実は、ArrayListの中で最も注意が必要で、かつ奥が深いのがこの削除操作なんです。

第3章:削除の罠(removeのオーバーロードと要素のシフト)

概要

要素を消すには remove メソッドを使いますが、これには「場所(番号)で指定する」方法と「中身(オブジェクト)で指定する」方法の2種類があります。これらがどう使い分けられ、コンピュータ内部で何が起きているのかを理解しましょう。

対象メソッド

  • remove(int index)
  • remove(Object o)

サンプルコード

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

public class Main {
    public static void main(String[] args) {
        List<String> trainees = new ArrayList<>();
        trainees.add("佐藤");
        trainees.add("高橋");
        trainees.add("鈴木");

        // 1. インデックス指定での削除
        String removedName = trainees.remove(1);
        System.out.println("削除されたのは: " + removedName);

        // 2. オブジェクト指定での削除
        boolean isDeleted = trainees.remove("佐藤");
        System.out.println("佐藤さんは消せた?: " + isDeleted);

        System.out.println("最終的なリスト: " + trainees);
    }
}

実行結果

削除されたのは: 高橋 佐藤さんは消せた?: true 最終的なリスト: [鈴木]

逐次解説

  1. List<String> trainees = new ArrayList<>(); String型のリストを作ります。
  2. trainees.add("佐藤"); trainees.add("高橋"); trainees.add("鈴木"); 3つの要素を追加しました。
  3. String removedName = trainees.remove(1); インデックス1番(高橋)を削除します。このメソッドは「削除された要素」を返します。
  4. boolean isDeleted = trainees.remove("佐藤"); 「佐藤」という文字列を検索して削除します。このメソッドは「削除に成功したか(見つかったか)」を true/false で返します。
  5. System.out.println("最終的なリスト: " + trainees); 高橋と佐藤が消え、鈴木だけが残ります。

イメージ

削除が行われるとき、ArrayListの内部では「詰め物」の作業が発生しています。

  • 初期状態 [ 0:佐藤 | 1:高橋 | 2:鈴木 ]
  • remove(1) を実行 1番の高橋が消えます。すると、2番にいた鈴木が「左へ1つズレて」空いた穴を埋めます。 [ 0:佐藤 | 1:鈴木 | (空き) ]

このように、削除された位置より後ろにあるすべての要素が左に移動します。

よくある誤解

数値(Integer型)を格納しているリストで remove(1) と書くと、「1という数字」を消したいのか「1番目の要素」を消したいのか、Javaが迷ってしまうことがあります。 デフォルトでは int index 版が優先されるため、数字の「1」を消したい場合は remove(Integer.valueOf(1)) と書く必要があります。これは新人がよくハマるポイントですよ!

実務での重要性

第1章の追加と同様に、削除も「要素のシフト(移動)」が発生します。 先頭付近の要素を削除すると、後ろにある数万件のデータをすべて左にずらす必要があるため、非常に動作が重くなります。大量のデータを頻繁に削除するプログラムでは、設計の見直しが必要かもしれません。

演習問題

問1:要素が「A, B, C, D, E」の順で並んでいるとき、remove(2) を実行した直後の各要素のインデックス(番号)を答えてください。 問2:remove("Z") を実行したとき、リストの中に "Z" が存在しなかった場合、プログラムはエラーで止まるでしょうか。それとも何かが返ってくるでしょうか。


次は、特定のデータがリストの中にあるかどうかを調べる方法について学びましょう!

第4章では、リストの中に特定のデータが入っているか、あるいは今どれくらいのデータがあるのかを調べる「検索と状態確認」について学びます。 プログラムを書いていると、「もし、このユーザーがリストにいたら処理をする」といった条件分岐がよく出てきますよね!

第4章:検索と状態確認(contains、indexOf、isEmptyの裏側)

概要

リストの中から特定の要素を探し出す方法と、リストが空っぽかどうかをスマートに判定する方法を習得します。ここで重要なのは、Javaがどうやって「同じもの」だと判断しているかという内部の仕組みです。

対象メソッド

  • contains(Object o)
  • indexOf(Object o)
  • isEmpty()
  • size()

サンプルコード

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

public class Main {
    public static void main(String[] args) {
        List<String> items = new ArrayList<>();
        
        System.out.println("リストは空?: " + items.isEmpty());
        
        items.add("Java");
        items.add("Python");
        items.add("Java"); // 重複して追加
        
        System.out.println("Javaは含まれる?: " + items.contains("Java"));
        System.out.println("PHPは含まれる?: " + items.contains("PHP"));
        
        System.out.println("最初のJavaのインデックス: " + items.indexOf("Java"));
        System.out.println("現在の要素数: " + items.size());
    }
}

実行結果

リストは空?: true Javaは含まれる?: true PHPは含まれる?: false 最初のJavaのインデックス: 0 現在の要素数: 3

逐次解説

  1. items.isEmpty() リストのサイズが 0 かどうかを判定します。 size() == 0 と書くよりも意図が伝わりやすく、読みやすいコードになります。
  2. items.contains("Java") リストの中に "Java" という文字列があるか探します。あれば true、なければ false を返します。
  3. items.indexOf("Java") "Java" が最初に見つかった位置(インデックス)を返します。もし見つからなければ -1 が返ってきます。
  4. items.size() 現在リストに格納されている要素の個数を返します。

イメージ

ArrayListの検索は、端から順番にチェックしていく「線形探索(リニアサーチ)」という方法で行われます。

  • contains("Python") を実行した場合
    1. インデックス0(Java)を見る → "Python" じゃない
    2. インデックス1(Python)を見る → 当たり! true を返す

このように、前から順番に 1つずつ equals メソッドを使って比較しているのです。

よくある誤解

「contains は魔法のように一瞬で見つけ出してくれる」と思っていませんか。 実は、リストが100万件あれば、最悪の場合100万回の比較を行います。要素数が増えれば増えるほど、検索にかかる時間は長くなるという特性を覚えておいてください。 また、比較には equals メソッドが使われます。自作のクラス(例えば User クラスなど)をリストに入れる場合は、equals を正しくオーバーライドしておかないと、中身が同じでも「見つからない」という事態が起こります。

実務での重要性

size() > 0 でリストが空でないことを確認するコードをよく見かけますが、現場では !isEmpty() を使うのが推奨されることが多いです。「空ではない(Not Empty)」という言葉の通りなので、直感的に理解しやすいからです。 また、重複を許さないリストを作りたいときに if (!list.contains(item)) { list.add(item); } と書くことがありますが、データ量が多い場合は非常に効率が悪いため、別のデータ構造(Setなど)を検討するタイミングかもしれません。

演習問題

問1:リストの中に同じ値が複数含まれている場合、indexOf メソッドはどの位置を返すでしょうか。 問2:要素が3つ入っているリストに対して isEmpty() を実行したときの戻り値と、その時の size() の値を答えてください。


次は、リストを一気に掃除したり、別の形式に変換したりするテクニックを学びましょう。

第5章:一括操作とメモリ管理(clear、toArrayとガベージコレクション)

概要

リストの全要素を一瞬で削除する clear と、リストを標準的な配列に変換する toArray を紹介します。特に clear の背後で動く、Javaの掃除屋さん「ガベージコレクション」との関係は非常に重要です。

対象メソッド

  • clear()
  • toArray()
  • toArray(T[] a)

サンプルコード

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

public class Main {
    public static void main(String[] args) {
        List<String> items = new ArrayList<>();
        items.add("掃除機");
        items.add("洗濯機");

        // 1. 配列への変換
        String[] array = items.toArray(new String[0]);
        System.out.println("配列の0番目: " + array[0]);

        // 2. リストの全削除
        items.clear();
        System.out.println("clear後のサイズ: " + items.size());
    }
}

実行結果

配列の0番目: 掃除機 clear後のサイズ: 0

逐次解説

  1. List<String> items = new ArrayList<>(); String型のリストを生成し、2つの要素を追加します。
  2. String[] array = items.toArray(new String[0]); リストの中身を、新しい String型の配列として取り出します。引数に new String[0] を渡すのは、Javaの型情報を教えるための定石です。
  3. items.clear(); リスト内のすべての要素(参照)を削除し、サイズを 0 にします。
  4. items.size() 要素がすべて消えたため、結果は 0 になります。

イメージ

clear を実行したとき、ArrayListは内部の配列に格納されていた「オブジェクトへの道しるべ(参照)」をすべて切り離します。

  1. 実行前:[リスト] → [ "掃除機" , "洗濯機" ]
  2. 実行後:[リスト] → [ null , null ]

道しるべを失い、誰からも使われなくなった "掃除機" オブジェクトなどは、Javaの「ガベージコレクション(GC)」という仕組みによって自動的にメモリから回収・消去されます。

よくある誤解

clear を呼べば、すぐにメモリが空く」と思っていませんか。 実は、clear はあくまで「もう使いませんよ」という印をつけるだけで、実際にメモリを掃除するのはガベージコレクションのタイミング次第です。しかし、巨大なリストを使い終わった後に clear せずに放置すると、メモリ不足(OutOfMemoryError)の原因になるので注意しましょう。

実務での重要性

toArray は、古いライブラリや外部システムが「Listではなく配列でデータをください」と要求してくる場合によく使います。 また、clear は「同じリストのインスタンスを使い回して、中身だけ入れ替えたい」というループ処理の中で重宝されます。新しく new ArrayList() し直すよりも、同じ箱(リスト)を掃除して使い回すほうが、コンピュータへの負担が少ない場合があるからです。

演習問題

問1:clear メソッドを実行した後、そのリストに対して get(0) を実行するとどうなるでしょうか。 問2:toArray メソッドを使ってリストを配列に変換した後、その「配列」から要素を削除した場合、元の「リスト」の中身はどうなるでしょうか。


さて、次は新人エンジニアが最も苦戦するポイントの一つ、「繰り返し処理中の削除」について学びます。

第6章では、リストの中身を一つずつ取り出して処理する「反復処理(ループ)」について解説します。 実は、ここで「要素を消しながら回す」という操作を安易に行うと、プログラムが悲鳴を上げて止まってしまうことがあるんです。

第6章:反復処理の正解(拡張for文、Iterator、そして恐怖の例外)

概要

ArrayListの要素を順番に処理する方法はいくつかありますが、状況によって使い分ける必要があります。特に、ループの最中に要素を削除しようとすると発生する「ConcurrentModificationException」というエラーの回避方法を学ぶのがこの章のメインテーマです。

対象メソッド・構文

  • 拡張for文(for-each)
  • Iterator(イテレータ)
  • forEach(Consumer action) ※Java 8以降

サンプルコード

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

public class Main {
    public static void main(String[] args) {
        List<String> tasks = new ArrayList<>();
        tasks.add("メール返信");
        tasks.add("会議");
        tasks.add("資料作成");

        // 1. 拡張for文(読み取り専用に最適)
        for (String task : tasks) {
            System.out.println("タスク: " + task);
        }

        // 2. Iterator(ループ中の削除が可能)
        Iterator<String> it = tasks.iterator();
        while (it.hasNext()) {
            String task = it.next();
            if (task.equals("会議")) {
                it.remove(); // 安全に削除できる
            }
        }
        System.out.println("残りのタスク: " + tasks);
    }
}

実行結果

タスク: メール返信 タスク: 会議 タスク: 資料作成 残りのタスク: [メール返信, 資料作成]

逐次解説

  1. for (String task : tasks) 拡張for文です。内部的には「Iterator」が動いていますが、見た目がスッキリしていて最もよく使われます。
  2. Iterator<String> it = tasks.iterator(); リストを指し示す「指(イテレータ)」を生成します。
  3. while (it.hasNext()) 「次の要素はあるかな?」と確認し、ある間だけループを続けます。
  4. String task = it.next(); 次の要素へ指を動かし、その中身を取得します。
  5. it.remove(); 現在指している要素をリストから安全に削除します。

イメージ

拡張for文は「自動で動くベルトコンベア」のようなものです。動いているベルトコンベアの上から、勝手に荷物(要素)を抜き取ると、機械が「数が合わない!」とパニックを起こして停止します。これがエラーの正体です。

一方で Iterator は、自分が今どこを指しているかを正確に把握している「専用の作業員」です。この作業員に「今持っている荷物を捨てて」と頼む(it.remove)ことで、ベルトコンベアを止めずに安全に処理を続けられます。

よくある誤解

「拡張for文の中で list.remove(task) を呼んでも、1回だけなら大丈夫だろう」と思われがちですが、たとえ1回でもエラーになります。Javaは「ループ中にリストの構造が変わること」を非常に厳しくチェックしているからです。

実務での重要性

現場では、条件に合うデータだけを取り除きたい場面が多々あります。その際、Iterator を使うか、あるいは後述する「Stream APIの filter」を使うのが正解です。 「ConcurrentModificationException(並行修正例外)」という長い名前のエラーが出たら、「あ、ループ中に中身をいじっちゃったな」とすぐに気づけるようになってくださいね!

演習問題

問1:拡張for文の中で list.add("新しい要素") を実行した場合、プログラムはどうなるでしょうか。 問2:Java 8から導入された list.removeIf(task -> task.equals("会議")) という書き方は、内部でどのような仕組み(今回学んだもの)を利用していると推測できますか。


いよいよ終盤です。第7章では、ArrayListの「親戚」との比較や、設計上の重要ルールについてお話しします。

第7章では、ArrayListを単体で使うのではなく、より広い視点から「どう設計に組み込むか」というプロフェッショナルな考え方を学びましょう。 「動けばいい」から「メンテナンスしやすい」プログラムへのステップアップです!

第7章:設計の極意(Listインターフェースの活用とLinkedListとの使い分け)

概要

ArrayListは便利なクラスですが、実は「ArrayList」として直接扱うよりも、「List」という大きな枠組み(インターフェース)として扱うほうが現場では好まれます。また、よく比較される「LinkedList」との違いを理解し、データ量や操作内容に応じて最適な道具を選べるようになりましょう。

対象メソッド・概念

  • List(インターフェース)
  • ArrayList と LinkedList の比較
  • 計算量(O記法)のさわり

サンプルコード

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

public class Main {
    public static void main(String[] args) {
        // 左側を List インターフェースにするのが鉄則!
        List<String> list1 = new ArrayList<>();
        List<String> list2 = new LinkedList<>();

        process(list1);
        process(list2);
    }

    // 引数を List 型にすることで、どちらのリストも受け取れる
    private static void process(List<String> list) {
        list.add("データ");
        System.out.println("処理完了: " + list.getClass().getSimpleName());
    }
}

実行結果

処理完了: ArrayList 処理完了: LinkedList

逐次解説

  1. List<String> list1 = new ArrayList<>(); ArrayListを作りますが、型は「List」として宣言します。
  2. List<String> list2 = new LinkedList<>(); LinkedList(連結リスト)を作ります。これも型は「List」です。
  3. process(list1); メソッドにリストを渡します。
  4. private static void process(List<String> list) このメソッドは「Listインターフェース」を引数に取っています。これにより、中身が ArrayList だろうが LinkedList だろうが、同じように処理できる「汎用性」が生まれます。

イメージ

ArrayList と LinkedList は、中身の構造が全く違います。

  • ArrayList(配列構造) [ 0 | 1 | 2 | 3 ] 番号がついたロッカーのようなものです。3番のロッカーを開けるのは一瞬ですが、途中に荷物を入れるには後ろを全部ずらす必要があります。
  • LinkedList(連結構造) [A] <-> [B] <-> [C] 手をつないで並んでいる人たちのようなものです。途中に新しい人が入るには、前後で手をつなぎ直すだけなので一瞬ですが、「100番目の人」を探すには先頭から順に数えていかなければなりません。

よくある誤解

「とりあえず LinkedList のほうがなんとなく凄そう」と思って使いがちですが、現代のコンピュータでは ArrayList のほうが圧倒的に速い場面が多いです。メモリを連続して使う ArrayList は CPU にとって処理しやすく、LinkedList は要素ごとにメモリがバラバラなので、実は検索以外でも遅くなることがあります。迷ったらまずは ArrayList を使いましょう。

実務での重要性

現場では、変数の宣言やメソッドの戻り値には List を使います。 もし将来的に「やっぱり ArrayList より LinkedList のほうが効率がいい」と分かったとき、左側の宣言が List になっていれば、右側の new する部分を書き換えるだけで済み、他のコードを修正する必要がなくなるからです。

演習問題

問1:データの「読み取り(get)」が非常に多いシステムでは、ArrayList と LinkedList のどちらを採用すべきでしょうか。 問2:List<String> names = new ArrayList<>(); という書き方を推奨する理由を「拡張性」という言葉を使って説明してください。

いよいよ最終章です!ここまでArrayListの基本的な操作や設計思想を学んできましたね。 最後は、現代のJava開発(Java 8以降)で欠かせない、スマートでカッコいいデータの扱い方「Stream API」との連携について解説します。

第8章:モダンJavaへの招待(Stream APIとの連携)

概要

これまでの章では、for文を使って「どうやって(How)」処理するかを書いてきました。Stream APIを使うと、「何を(What)」したいのかを直感的に記述できるようになります。ArrayListに溜まったデータを、まるで工場のベルトコンベア(流れる川=Stream)に乗せて、一気に加工・選別するイメージです!

対象メソッド

  • stream()
  • filter(Predicate)
  • map(Function)
  • forEach(Consumer)
  • collect(Collectors.toList())

サンプルコード

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

public class Main {
    public static void main(String[] args) {
        List<String> programmingLanguages = new ArrayList<>();
        programmingLanguages.add("Java");
        programmingLanguages.add("Python");
        programmingLanguages.add("JavaScript");
        programmingLanguages.add("C++");
        programmingLanguages.add("Ruby");

        // 「J」から始まる言語だけを抜き出して、すべて大文字に変換した新しいリストを作る
        List<String> jLanguages = programmingLanguages.stream()
            .filter(lang -> lang.startsWith("J")) // 絞り込み
            .map(lang -> lang.toUpperCase())      // 加工
            .collect(Collectors.toList());        // リストに戻す

        System.out.println("元のリスト: " + programmingLanguages);
        System.out.println("加工後のリスト: " + jLanguages);
    }
}

実行結果

元のリスト: [Java, Python, JavaScript, C++, Ruby] 加工後のリスト: [JAVA, JAVASCRIPT]

逐次解説

  1. programmingLanguages.stream() ArrayListから「Stream(データの流れ)」を生成します。ここから加工のスタートです。
  2. .filter(lang -> lang.startsWith("J")) 「Jから始まるものだけを通すフィルター」を設置します。
  3. .map(lang -> lang.toUpperCase()) 流れてきた文字を「すべて大文字に変える加工」を施します。
  4. .collect(Collectors.toList()) 流れてきたデータを再び「List」という容器に回収します。
  5. 元のリストはそのまま Stream操作をしても、元の programmingLanguages リストの中身は一切変わりません(非破壊的と言います)。

イメージ

工場の製造ラインを想像してください。

  • ArrayList:倉庫(データが保管されている場所)
  • stream():倉庫から商品を取り出してベルトコンベアに乗せる
  • filter:不良品(条件に合わないもの)を取り除く検品作業
  • map:ラベルを貼る、色を塗るなどの加工
  • collect:完成品を新しい箱(新しいList)に詰め直す

よくある誤解

「Streamを使えば処理が常に速くなる」というのは誤解です。 単純なループであれば、従来のfor文の方が速いこともあります。Streamの最大のメリットは、速度よりも「コードの読みやすさ」と「バグの入りにくさ」にあります。一目で「何をしているか」が分かるコードは、チーム開発において最強の武器になります。

実務での重要性

現場のコードレビューでは「ここ、Streamで書けるよね?」と指摘されることが多々あります。 また、第6章で学んだ「ループ中の削除による例外」も、Streamの filter を使って「必要なものだけ残した新リストを作る」というアプローチをとれば、安全かつスマートに解決できます。

演習問題

問1:リスト内の数値のうち「50以上のものだけを表示する」処理を、Streamの filterforEach を使って書いてみてください。 問2:Stream操作を行った後、元のArrayListの中身はどうなっているでしょうか。


まとめと今後の学習指針

お疲れ様でした!これでArrayListの主要な知識を網羅しました。 最後に、皆さんがさらに成長するためのロードマップを提示します。

  1. まずは使い倒す: 日々のコーディングで、積極的にArrayListを使ってみてください。特に List 型で宣言する癖をつけましょう。
  2. 計算量を意識する: 「この処理はリストが1万件になっても大丈夫かな?」と考える癖をつけてください。これがエンジニアとしての「勘」を養います。
  3. 他のコレクションを学ぶ: ArrayListの弱点(検索が遅い、重複を許す)を知った今、次は HashSet(重複を許さない、検索が爆速)や HashMap(キーと値のペア)を学習する絶好のタイミングです。
  4. Stream APIを深掘りする: 今回は触りのみでしたが、Streamにはまだまだ便利な機能がたくさんあります。

ArrayListは、Javaエンジニアにとって最も頼れる相棒です。 その中身(仕組み)を知っているあなたなら、もう自信を持って使いこなせるはずですよ!

セイ・コンサルティング・グループでは新人エンジニア研修のアシスタント講師を募集しています。

投稿者プロフィール

山崎講師
山崎講師代表取締役
セイ・コンサルティング・グループ株式会社代表取締役。
岐阜県出身。
2000年創業、2004年会社設立。
IT企業向け人材育成研修歴業界歴20年以上。
すべての無駄を省いた費用対効果の高い「筋肉質」な研修を提供します!
この記事に間違い等ありましたらぜひお知らせください。

学生時代は趣味と実益を兼ねてリゾートバイトにいそしむ。長野県白馬村に始まり、志賀高原でのスキーインストラクター、沖縄石垣島、北海道トマム。高じてオーストラリアのゴールドコーストでツアーガイドなど。現在は野菜作りにはまっている。