![]()
【Java入門 実用編】Stream API を活用した型の相互変換 | コレクションクラスの活用
2026.02.27
前回までの記事では、Javaで最もよく使われる List をはじめ、Set、Deque(キュー)、Map といった代表的なコレクションにおいて、Stream APIを利用したデータ操作(絞り込みや加工など)を行う方法を解説しました。
当記事では、これらの「コレクション間の変換パターン」と、Stream APIを利用した具体的な実装方法について解説します。

目次
型の変換が必要なケースとは?
実際のシステム開発では「目的に合わせてデータ構造(コレクションの型)を作り変える」ということがあります。
例えば「リストの中から重複を排除したい」「リストのデータを、キーで検索できる辞書型(Map)に変換したい」「処理待ちのタスクをキュー(Deque)に流し込みたい」といったケースや、利用したいライブラリや共通処理のメソッド間で戻り値や引数の型に違いがある場合など、様々な場面でコレクションの型の変換が必要となります。
このような場合、従来は for 文を回して新しいコレクションに一つずつ格納していくのが一般的でしたが、Java 8 以降は Stream API の collect メソッド を利用することで、非常に簡潔かつ安全にコレクション間の相互変換を行うことができます。
List と Set の相互変換
一般的に List は「順序があり、重複を許す」コレクションですが、Set は「順序を保証せず、重複を許さない」という真逆の特徴を持っています(※LinkedHashSet などの一部の実装クラスを除きます)。
これらのコレクション間の型の変換を行うことで、重複の排除や順序付けといったデータクレンジング(整形)を行うことができます。
List → Set への変換
文字列などの基本的なデータであれば、List の Stream を既定のコレクター(Collectors.toSet メソッドで取得できるコレクター)を利用して Set へ変換するだけで、簡単に重複する要素を除外することができます。
以下の例では、「最近閲覧した商品のカテゴリ」の全データから、重複するカテゴリを除いて「興味のあるカテゴリ一覧」を抽出しています。
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
public class ListToSetSample {
public static void main(String[] args) {
// 重複を含む List のデータ(最近閲覧した商品のカテゴリ)
List<String> categoryList = Arrays.asList(
"Book", "Electronics", "Book", "Fashion", "Electronics", "Food"
);
System.out.println("元の List: " + categoryList);
// Stream を使って Set に変換(自動的に重複する要素が除去される)
Set<String> uniqueCategories = categoryList.stream()
.collect(Collectors.toSet());
System.out.println("変換後の Set: " + uniqueCategories);
}
}
実行結果:
元の List: [Book, Electronics, Book, Fashion, Electronics, Food]
変換後の Set: [Book, Food, Fashion, Electronics]
また、文字列などの基本型だけでなく、Java Beans のような独自のクラスであっても、クラス内に equals メソッドと hashCode メソッドが正しく定義(オーバーライド)されていれば、同様に Set への変換による重複排除が可能です。
Set → List への変換
上記とは逆に、Set に保持されているデータは、Set の Stream に対して 既定のコレクター(Collectors.toList メソッドで取得できるコレクター) を利用することで、簡単に List へ変換できます。
前項のように自ら重複を排除して Set を作成するケースの他に、Map からキーの一覧を取得する場合のように、Java の仕様として「強制的に Set 型でデータを受け取る」場面があります。
(Map のキーは重複が許されないため Set で管理されている)
例えば、都道府県コードをキーに持つ Map から、都道府県コード(キー)だけを List として他のメソッドに引き渡したい、といったケースです。以下の例では、取得した都道府県コードを自然順序でソートし、順序を保持できる List に移し替えて別のメソッドへ渡しています。
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
public class SetToListSample {
public static void main(String[] args) {
// 都道府県コードをキー、名前を値に持つ Map
Map<Integer, String> prefMap = new HashMap<>();
prefMap.put(13, "東京");
prefMap.put(1, "北海道");
prefMap.put(27, "大阪");
prefMap.put(14, "神奈川");
// Map からキーの一覧(Set)を取得
Set<Integer> prefCodes = prefMap.keySet();
System.out.println("Set: " + prefCodes);
// Stream を使ってソートし List に変換
List<Integer> sortedPrefCodes = prefCodes.stream()
.sorted() // コードの小さい順(自然順序)でソート
.collect(Collectors.toList());
// 変換した List を、別のメソッドに引き渡す
printCodes(sortedPrefCodes);
}
/**
* List を引数として受け取るメソッドのサンプル
*/
private static void printCodes(List<Integer> codes) {
System.out.println("List: " + codes);
// List なのでインデックス(0番目など)を指定して取得できる
System.out.println("先頭のコード: " + codes.get(0));
}
}
実行結果:
取得したキーの Set: [1, 13, 14, 27]
List: [1, 13, 14, 27]
先頭のコード: 1List と Deque の相互変換
List などのコレクションは、用途に合わせ Queue や Deque(両端キュー)といった「待ち行列」を扱うコレクションに変換して利用することができます。また、Queue や Deque 内の要素を確認したい場合、Stream API を経由してアクセスすることで、待ち行列に影響を及ぼさずに条件に合致する要素を抜き出してリスト化することができます。
List → Deque への変換
List に保持されているデータは、Stream の filter メソッドで条件を絞り込みつつ、「特定の条件を満たす要素だけを抽出した Deque」へ簡単に変換できます。
ここで注意点として、List や Set への変換とは異なり、Java 標準では Deque 専用のコレクターを取得するメソッド(Collectors.toXXX)は用意されていません。そのため、変換時は collect メソッド内で Collectors.toCollection メソッドを利用し、変換先として生成するクラス(ArrayDeque::new など)を明示的に指定する必要があります。
以下の例では、取得したタスクのリストから優先度3のタスクだけを ArrayDeque に変換し、先頭のタスクを取り出して(poll)処理しています。これは、データベースから一括取得した「全タスク一覧(List)」の中から、「優先度が3(高優先)のタスクだけ」を抽出し、先頭から順番に1件ずつ安全に消化(取り出し)していくためのキューとして扱いたいケースなどに有効な手法です。
import java.util.ArrayDeque;
import java.util.Arrays;
import java.util.Deque;
import java.util.List;
import java.util.stream.Collectors;
// 優先度とタスク名を持つデータクラス
class Task {
private int priority;
private String name;
public Task(int priority, String name) {
this.priority = priority;
this.name = name;
}
public int getPriority() { return priority; }
public String getName() { return name; }
@Override
public String toString() { return "[優先度:" + priority + "] " + name; }
}
public class ListToDequeSample {
public static void main(String[] args) {
// DBなどから取得した全タスクのリストを想定(優先度が混在)
List<Task> allTasks = Arrays.asList(
new Task(1, "Task-A"),
new Task(3, "Task-B"), // 優先度3
new Task(2, "Task-C"),
new Task(3, "Task-D") // 優先度3
);
System.out.println("List: " + allTasks);
//「優先度3」だけを抽出し ArrayDeque に変換
Deque<Task> highPriorityQueue = allTasks.stream()
.filter(task -> task.getPriority() == 3) // 優先度3で絞り込み
.collect(Collectors.toCollection(ArrayDeque::new));
System.out.println("優先度3のDeque: " + highPriorityQueue);
// Dequeの先頭から要素(Task)を取り出して処理(Dequeからは削除)
System.out.println("実行するタスク: " + highPriorityQueue.poll());
System.out.println("残タスク: " + highPriorityQueue);
}
}
実行結果:
List: [[優先度:1] Task-A, [優先度:3] Task-B, [優先度:2] Task-C, [優先度:3] Task-D]
優先度3のDeque: [[優先度:3] Task-B, [優先度:3] Task-D]
実行するタスク: [優先度:3] Task-B
残タスク: [[優先度:3] Task-D]Deque → List への変換
上記の例とは逆に、Deque の Stream に対して filter と既定のコレクター(Collectors.toList)を組み合わせることで、現在の待ち行列の中から特定の条件を満たすタスクだけを List へ簡単に変換・抽出することもできます。
例えば、Dequeで順番に処理している途中にエラーが発生した際、処理待ちキューに残っているタスクの中から「優先度が2以上のタスク」だけを List として抽出して別メソッド(状況通知メールを送信するなど)に引き渡したい、といったケースです。
以下の例では、処理途中のキューから条件に合致するタスクを List に変換し、別のメソッドへ渡して出力しています。
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.List;
import java.util.stream.Collectors;
public class DequeToListSample {
public static void main(String[] args) {
// 処理途中で様々な優先度のタスクが残っているキュー
Deque<Task> taskQueue = new ArrayDeque<>();
taskQueue.add(new Task(1, "Task-E"));
taskQueue.add(new Task(3, "Task-F"));
taskQueue.add(new Task(2, "Task-G"));
System.out.println("Deque: " + taskQueue);
// Stream を使い「優先度2以上」のタスクだけを List に変換
List<Task> urgentRemainingTasks = taskQueue.stream()
.filter(task -> task.getPriority() >= 2) // 優先度2以上で絞り込み
.collect(Collectors.toList());
// 変換した List をメソッドに引き渡す
processUrgentTasks(urgentRemainingTasks);
}
/**
* List を引数として受け取り処理するメソッド
*/
private static void processUrgentTasks(List<Task> tasks) {
System.out.println("引き渡されたList: " + tasks);
System.out.println("要対応のタスク件数は " + tasks.size() + " 件です。");
}
}
実行結果:
Deque: [[優先度:1] Task-E, [優先度:3] Task-F, [優先度:2] Task-G]
引き渡されたList: [[優先度:3] Task-F, [優先度:2] Task-G]
要対応のタスク件数は 2 件です。List から Map への変換
前述の通り、Map は直接他のコレクションへの変換はできませんが、キーの Set(Map#keySet)や値のCollection(Map#values)は List などのコレクションに変換可能です。
逆に、Set や List の要素については、Stream を利用して Map に変換することができます。例えば、何度もデータを確認する大きなサイズの List などは、事前に「特定のIDをキー、データそのものを値」とする Map に変換しておくことで、データ固有のキーを指定して抽出できるようにし、パフォーマンスの向上につなげることができます。
List → Map への変換
以下の例では、従業員(Employee)のリストから、ID をキーとした Map を作成しています。「全従業員リスト(List)」の中から、後続の処理で繰り返し従業員情報をIDで検索する必要がある、といったケースでは、このようにあらかじめ「従業員IDをキー、従業員オブジェクト自身を値」とする Map に変換しておくことで要素へのアクセスを容易にしています。
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
// 従業員データクラス
class Employee {
private int id;
private String name;
public Employee(int id, String name) { this.id = id; this.name = name; }
public int getId() { return id; }
public String getName() { return name; }
@Override
public String toString() { return name + "(ID:" + id + ")"; }
}
public class ListToMapSample {
public static void main(String[] args) {
// 従業員リストのデータを準備
List<Employee> employees = Arrays.asList(
new Employee(101, "Tanaka"),
new Employee(102, "Sato"),
new Employee(103, "Suzuki")
);
// Stream を使って、IDをキー、要素自身を値とする Map に変換
Map<Integer, Employee> employeeMap = employees.stream()
.collect(Collectors.toMap(
Employee::getId, // 第1引数:キーの指定(従業員ID)
e -> e // 第2引数:値の指定(従業員オブジェクト自身)
));
System.out.println("作成された Map: " + employeeMap);
// 作成した Map からID指定でデータを取得
System.out.println("ID(102): " + employeeMap.get(102));
}
}
実行結果:
作成された Map: {101=Tanaka(ID:101), 102=Sato(ID:102), 103=Suzuki(ID:103)}
ID(102): Sato(ID:102)
Set や Queue/Deque から Map に変換する場合も、List からの変換と同様に Stream で既定のコレクター(Collectors.toMap メソッドで取得できるもの)を利用することで Map 化することが可能です。(ここでは割愛します)
キーが重複した場合の動作
Map に変換するための既定のコレクター(Collectors.toMap)を利用する際、実務上絶対に気をつけるべき注意点があります。それは、「変換元のコレクション内に、同じキーとなる要素が複数存在した場合、例外(IllegalStateException)が発生する」という点です。
想定外のシステムエラーを防ぐための方法として、1つは try-catch で処理を囲み、エラー時の処理を行う方法が考えられます。これは、キー重複をデータの異常として検知したい場合の方法です。この場合、例外の発生時点で Stream の処理自体も中断されることに注意してください。
以下の例では、外部システムから連携された従業員リストの中に、誤って「同じID(102)を持つ別のデータ」が混ざっていたため、エラーログを出力して後続の処理を行わずに終了します。
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public class ListToMapTryCatchSample {
public static void main(String[] args) {
// 外部システムから連携されたリスト(ID:102が重複している)
List<Employee> employees = Arrays.asList(
new Employee(101, "Tanaka"),
new Employee(102, "Sato"),
new Employee(102, "Yamada") // 重複データ
);
Map<Integer, Employee> employeeMap = null;
try {
// 第3引数を指定せずにMapへ変換(重複があるため例外が発生する)
employeeMap = employees.stream()
.collect(Collectors.toMap(
Employee::getId,
e -> e
));
// 例外が発生するため、以下の処理は実行されない
System.out.println("変換成功: " + employeeMap);
} catch (IllegalStateException e) {
// キーの重複を異常として検知し、エラーログを出力する
System.err.println("【エラー】従業員IDの重複を検知しました。");
// メソッドを抜けて後続の処理を行わずに終了する
return;
}
System.out.println("この行(後続の処理)は実行されません。");
}
}
実行結果:
【エラー】従業員IDの重複を検知しました。
もう1つの方法として、キーが重複した時の動作が決まっている(正常な処理として処理を続行する)場合は、既定のコレクター(Collectors.toMap)の第3引数に「マージ関数(重複時のルール)」を指定し、キーが衝突した際の振る舞いを明示しておくことができます。
以下の例では、重複するIDが存在した場合に「先に処理された古いデータを残す(新しいデータは無視する)」というルールを指定して、List の全データを対象に Map への変換を行っています。
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public class ListToMapDuplicateSample {
public static void main(String[] args) {
// 同じID(102)が重複して含まれているリスト
List<Employee> employees = Arrays.asList(
new Employee(101, "Tanaka"),
new Employee(102, "Sato"), // 先に登録されているデータ
new Employee(102, "Yamada") // 重複データ(エラーの原因)
);
// 第3引数を指定して、重複時のルールを定義して Map に変換
Map<Integer, Employee> employeeMap = employees.stream()
.collect(Collectors.toMap(
Employee::getId,
e -> e,
// 第3引数:キーが重複した場合、(既存の値, 新しい値) -> どちらを残すか
(existingValue, newValue) -> existingValue // 今回は「既存の値」を優先する
// ※後から来た新しいデータで上書きしたい場合は newValue を返すようにします
));
// エラーで落ちることなく、安全に変換される
System.out.println("最終的なMap: " + employeeMap);
}
}
実行結果:
最終的なMap: {101=Tanaka(ID:101), 102=Sato(ID:102)}Map から List への変換
前述の Set から List の変換で、Map#keySet を利用して「キーの一覧」を取得し、List に変換する方法をご紹介しました。ここでは対照的に、Map に格納されている「実データ(値)」を抽出して List などのコレクションに変換する方法を解説します。
Map の値(values)から List と Set を作成
Map に保持されているデータのうち、値(Value)のみの一覧は Map#values メソッドで取得できます。Map のキーは重複しませんが、値は重複して登録されている可能性があるため、目的に合わせて List と Set を使い分けるのが効果的です。
例えば、「従業員ID」をキー、「所属部署名」を値とした Map があるとします。ここから「すべての所属データをそのまま抽出したい」場合は List に、「重複を取り除いて、現在稼働している部署の一覧だけを抽出したい」場合は Set に変換します。
以下の例では、同じ Map#values で取得される Collection から Stream を生成し、List と Set の両方に変換して結果を比較しています。
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
public class MapValuesToCollectionSample {
public static void main(String[] args) {
// 従業員IDをキー、所属部署名を値とする Map
Map<Integer, String> empDeptMap = new HashMap<>();
empDeptMap.put(101, "Sales"); // 営業部
empDeptMap.put(102, "IT"); // IT事業部
empDeptMap.put(103, "Sales"); // 営業部(重複)
empDeptMap.put(104, "HR"); // 人事部
// パターンA:List に変換(重複を含め、すべての値をそのまま抽出)
List<String> allDeptsList = empDeptMap.values().stream()
.collect(Collectors.toList());
System.out.println("List(全件): " + allDeptsList);
// パターンB:Set に変換(重複を排除して、ユニークな値だけを抽出)
Set<String> uniqueDeptsSet = empDeptMap.values().stream()
.collect(Collectors.toSet());
System.out.println("Set(重複除去): " + uniqueDeptsSet);
}
}
実行結果:
List(全件): [Sales, IT, Sales, HR]
Set(重複除去): [Sales, HR, IT]Map のキーと値を利用して List に変換する
Map#entrySet を利用することで、Map から「キーと値のペア(Map.Entry)」の Set を取得することができます。これを利用することで、「Map の値だけのリストを作成したいが、並び順は『キー』を基準にしたい」といったことも可能です。
以下は Stream を利用して「従業員ID(キー)の順番でソートされた、従業員データ(値)の List」を作成する例です。Stream に対して Map.Entry のキーでソートを行った後、map メソッドで Map.Entry の値だけを抜き出して List に変換しています。
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
// 従業員データクラス
class Employee {
private int id;
private String name;
public Employee(int id, String name) { this.id = id; this.name = name; }
public int getId() { return id; }
public String getName() { return name; }
@Override
public String toString() { return name + "(ID:" + id + ")"; }
}
public class MapEntrySetToListSample {
public static void main(String[] args) {
// IDをキー、従業員オブジェクトを値とする Map
Map<Integer, Employee> employeeMap = new HashMap<>();
employeeMap.put(103, new Employee(103, "Suzuki"));
employeeMap.put(101, new Employee(101, "Tanaka"));
employeeMap.put(102, new Employee(102, "Sato"));
// entrySetを使い、キー(ID)でソートしてから値だけのListに変換
List<Employee> sortedByIdList = employeeMap.entrySet().stream()
// 1. Map.Entry のキー(従業員ID)を使って昇順ソート
.sorted(Map.Entry.comparingByKey())
// 2. Map.Entry から値(従業員オブジェクト)だけを抽出
.map(Map.Entry::getValue)
// 3. List として収集
.collect(Collectors.toList());
System.out.println("ID順(キー順)に抽出した従業員リスト:");
for (Employee emp : sortedByIdList) {
System.out.println(emp);
}
}
}
実行結果:
ID順(キー順)で抽出された従業員リスト:
Tanaka(ID:101)
Sato(ID:102)
Suzuki(ID:103)まとめ
当記事では、実務のシステム開発で頻出する「コレクション間の型変換」について、Stream API を活用した実践的な手法を解説しましたが、いかがでしたでしょうか。
データ構造(コレクションの型)には、それぞれ特性や処理の得意・不得意があります。利用する外部APIの仕様や実現したい業務要件に応じて、今回ご紹介した Stream API によるコレクションの相互変換をお役立てください。


