[Javaの基礎]Listについて
はじめに
ここでは以下についてまとめています。
-
List
について -
ArrayList
とLinkedList
の違いについて -
ArrayList
の使用方法 -
List
の主なメソッド -
List
でのループ処理 Stream API
Listとは
List
とは要素を順序付きで格納できるコレクションで、配列よりも柔軟にデータを扱えるのが特徴です。サイズは可変で、データの追加・削除・検索が簡単に行えます。また、要素の重複を許可しておりインデックスでアクセスが可能になってます。
コレクションは参照型しか格納できません。ArrayList<int>
のようにプリミティブ型はコレクションに直接格納できなくなっているのでラッパークラス(Integer
など)を使用します。
java.util.List
にはインターフェースで以下のような実装クラスがあります。
- 主な実装クラス
-
ArrayList
(もっとも一般的) LinkedList
-
Vector
(非推奨気味)
-
ArrayList
vs LinkedList
以下の表のようにArrayList
とLinkedList
はどちらもList
インターフェースを実装するクラスですが、内部構造がまったく異なるため、性能特性にも大きな違いがあります。
特徴 | ArrayList |
LinkedList |
---|---|---|
末尾へ要素の追加 | 高速 | やや遅め |
挿入・削除 | 遅い(要素への移動が必要) | 高速(ポインタ操作) |
ランダムアクセス | 高速(配列ベース) | 遅い(リストをたどる必要) |
- 末尾への追加
-
ArrayList
:高速- 内部は可変長配列で管理。
- 末尾への追加は、空きがあれば O(1) の時間で実行可能。
- ただし、配列容量を超えると拡張(再コピー)が発生し、その際に時間がかかる(O(n))。
-
LinkedList
:やや遅い- 内部は双方向リンク構造。
- 末尾のノードをたどって追加するが、ポインタの更新が必要なので
ArrayList
よりわずかに遅いことがある。 - ただし、頻繁な挿入・削除がある場合は有利。
- 挿入・削除(中間の位置)
-
ArrayList
:遅い- 挿入や削除後、後ろの要素をすべて1つずらす必要があるため、O(n)のコスト。
-
LinkedList
:高速- リスト内のノードを見つけた後、前後のノードのポインタを変更するだけで済む。
- 挿入・削除のコストはノードの検索を除けば O(1)。
- ランダムアクセス
-
ArrayList
:高速- 内部配列のインデックスを直接指定できるため、O(1) で任意の要素にアクセスできる。
-
LinkedList
:遅い- インデックスでアクセスすると、先頭から順にノードをたどる必要があり、O(n) のコストがかかる。
ArrayList
の使い方
以下のようにList<String> names = new ArrayList<>();
で空のリストを作成できます。
import java.util.*;
public class ListExample {
public static void main(String[] args) {
List<String> names = new ArrayList<>();
names.add("Alice");
names.add("Bob");
names.add("Charlie");
System.out.println(names.get(1)); // Bob
names.remove("Alice");
System.out.println(names); // [Bob, Charlie]
}
}
ちなみに、要素を持つリストを作成したい場合は次のようにします。
List<String> names = new ArrayList<>(List.of("Alice", "Bob", "Charlie"));
List
の主なメソッド
add
末尾に要素を追加します。
List<String> list = new ArrayList<>();
list.add("apple");
list.add("banana");
System.out.println(list); // [apple, banana]
以下のように指定したインデックスに要素を挿入することもできます。
List<String> list = new ArrayList<>();
list.add("apple");
list.add("banana");
list.add(1, "orange");
System.out.println(list); // [apple, orange, banana]
get
指定したインデックスの要素を返します。
// list = ["apple", "orange", "banana"]
String fruit = list.get(1);
System.out.println(fruit); // orange
set
指定したインデックスの要素を上書きします。
インデックスが存在しないと例外は発生します。
// list = ["apple", "orange", "banana"]
list.set(1, "grape");
System.out.println(list); // [apple, grape, banana]
remove
インデックスを指定して要素を削除します。
削除成功時にはtrue
が返ってきます。
// list = ["apple", "grape", "banana"]
list.remove(1); // "grape" を削除
System.out.println(list); // [apple, banana]
また、オブジェクトを指定して削除もできます。
// list = ["apple", "banana"]
list.remove("apple");
System.out.println(list); // [banana]
removeIf
コレクションの要素を条件に基づいて削除できます。条件がtrue
を返した要素は削除される。
list.removeIf(element -> 条件);
使用例
・リストから偶数要素を削除する。
List<Integer> numbers = new ArrayList<>(List.of(1, 2, 3, 4, 5, 6));
numbers.removeIf(n -> n % 2 == 0); // 偶数を削除
System.out.println(numbers); // → [1, 3, 5]
・文字列のフィルタリング
List<String> names = new ArrayList<>(List.of("Alice", "Bob", "Admin", "Tom"));
names.removeIf(name -> name.startsWith("A"));
System.out.println(names); // → [Bob, Tom]
contains
リスト内に含まれているかを確認し、存在する時はtrue
、存在しない場合にはfalse
が返ってきます。
// list = ["banana"]
System.out.println(list.contains("banana")); // true
System.out.println(list.contains("peach")); // false
size
リストの要素数を取得します。
// list = ["apple", "orange", "banana"]
System.out.println(list.size()); // 3
clear
要素が全て削除され、空の状態になります。
//list = ["apple", "orange", "banana"]
list.clear();
System.out.println(list); // []
isEmpty
空かどうかを判定します。size() == 0
と同じだがこちらの方が読みやすい。
//list = []
System.out.println(list.isEmpty()); // true
ループの使用
拡張for文
リストに対するループの方法の中でも拡張for文はシンプルで安全な方法でよく使用されます。
import java.util.*;
public class ForEachExample {
public static void main(String[] args) {
List<String> fruits = new ArrayList<>(List.of("Apple", "Banana", "Cherry"));
for (String fruit : fruits) {
System.out.println(fruit);
}
}
}
出力
Apple
Banana
Cherry
インデックス付きループ
インデックスが必要な場合はインデックス付きループを使用します。
for (int i = 0; i < fruits.size(); i++) {
System.out.println(i + ": " + fruits.get(i));
}
forEach
Java8から、Collection
インターフェースにforEach
メソッドが追加され、ラムダ式と一緒に使うことで簡潔にループ処理が書くことができます。
List<String> fruits = Arrays.asList("apple", "banana", "cherry");
fruits.forEach(fruit -> System.out.println(fruit));
また、以下のようにメソッド参照でラムダ式を簡略表記することができます。
fruits.forEach(System.out::println);
Stream API
Java8から登場したStream
は、コレクションのデータをパイプライン的に処理できるAPIです。複雑なデータ処理・集計・変換の際に使用します。元のコレクションは変更されず並列処理にも対応できます。
collection.stream()
.filter(...) // 中間操作
.map(...) // 中間操作
.collect(...); // 終端操作
・主な中間操作のメソッド
メソッド | 説明 |
---|---|
filter(Predicate) |
条件に合う要素だけを通す |
map(Function) |
各要素を変換する |
sorted() |
ソートする |
distinct() |
重複を排除する |
limit(n) /skip()
|
n件だけに制限/n件スキップする |
・主な終端操作のメソッド
メソッド | 説明 |
---|---|
forEach(Consumer) |
要素ごとに出力する |
collect(Collectors.toList()) |
Listなどに変換する |
count() |
要素数カウントする |
anyMatch()```,``` allMatch() |
条件に合う要素の有無チェック |
reduce() |
集計処理 |
使用例
- 以下は
a
から始める要素を大文字にして新しいリストを作成しています。
List<String> names = new ArrayList<>();
names.add("Alice");
names.add("Bob");
names.add("Charlie");
names.add("Anna");
List<String> result = names.stream()
.filter(name -> name.startsWith("A")) // "Alice", "Anna"
.map(String::toUpperCase) // "ALICE", "ANNA"
.collect(Collectors.toList());
System.out.println(result); // [ALICE, ANNA]
- 合計値を求める際の処理は以下のようになります。
以下の.reduce(0, Integer::sum);
は初期値を0とし、Integer::sum
で2つのInteger
を加算するメソッド参照しています。((a, b) -> a + b
と同じ)
List<Integer> numbers = new ArrayList<>(List.of(1, 2, 3, 4, 5));
int sum = numbers.stream()
.reduce(0, Integer::sum); // 初期値0で合計を計算
System.out.println(sum); // 15
上記のreduce()
は以下のようになっています。
初期値 = 0
→ 0 + 1 = 1
→ 1 + 2 = 3
→ 3 + 3 = 6
→ 6 + 4 = 10
→ 10 + 5 = 15
- リストから偶数を抽出して平方に変換し、ソートして出力する処理は以下になります。
public class StreamExample {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(5, 3, 8, 2, 4, 6);
List<Integer> result = numbers.stream()
.filter(n -> n % 2 == 0) // 偶数フィルタ
.map(n -> n * n) // 平方に変換
.sorted() // 昇順ソート
.collect(Collectors.toList()); // リストに変換
System.out.println(result); // [4, 16, 36, 64]
}
}
Discussion