zip処理は「2つの列を“ペアの列”にする」技
zip(ジップ)処理は、
「2つのコレクションを、同じ位置同士でペアにしていく」操作です。
名前のリスト
点数のリスト
この2つを「名前+点数」のペアにしたい——これがまさに zip の出番です。
“2本の線を、同じ長さだけチャックで閉じていく”イメージを持ってください。
まずは素朴な for 文での zip 処理
同じ長さの2つの List をペアにする
一番基本の形からいきます。
import java.util.ArrayList;
import java.util.List;
public class ZipBasic {
public static void main(String[] args) {
List<String> names = List.of("山田", "佐藤", "鈴木");
List<Integer> scores = List.of(80, 90, 75);
List<String> result = new ArrayList<>();
int size = Math.min(names.size(), scores.size());
for (int i = 0; i < size; i++) {
String name = names.get(i);
Integer score = scores.get(i);
result.add(name + " : " + score);
}
System.out.println(result); // [山田 : 80, 佐藤 : 90, 鈴木 : 75]
}
}
Javaここでの重要ポイントは二つです。
一つ目は、「Math.min(names.size(), scores.size()) で“短い方の長さ”に合わせている」ことです。
片方が長くても、はみ出した分は無視する——これが zip の基本的な考え方です。
二つ目は、「同じインデックス i で両方の List から要素を取り出し、ペアとして扱っている」ことです。
“位置で結びつける”という感覚をしっかり持ってください。
ペアを表す小さなクラスを用意する
Pair<L, R> という「2つ組」を作る
zip 処理を汎用的に使いたいなら、「ペア」を表すクラスを一つ持っておくと便利です。
public final class Pair<L, R> {
private final L left;
private final R right;
public Pair(L left, R right) {
this.left = left;
this.right = right;
}
public L getLeft() { return left; }
public R getRight() { return right; }
}
Javaこれを使って、zip ユーティリティを書きます。
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public final class Zips {
private Zips() {}
public static <L, R> List<Pair<L, R>> zip(List<L> left, List<R> right) {
if (left == null || right == null) {
return List.of();
}
int size = Math.min(left.size(), right.size());
if (size == 0) {
return List.of();
}
List<Pair<L, R>> result = new ArrayList<>(size);
for (int i = 0; i < size; i++) {
result.add(new Pair<>(left.get(i), right.get(i)));
}
return Collections.unmodifiableList(result);
}
}
Java使い方はこうなります。
List<String> names = List.of("山田", "佐藤", "鈴木");
List<Integer> scores = List.of(80, 90, 75);
List<Pair<String, Integer>> zipped = Zips.zip(names, scores);
for (Pair<String, Integer> p : zipped) {
System.out.println(p.getLeft() + " : " + p.getRight());
}
Javaここで深掘りしたいポイントは三つです。
一つ目は、「Pair<L, R> という“名前付きの2つ組”を使うことで、コードの意図が読みやすくなる」ことです。Map.Entry などで代用することもできますが、Pair の方が意味がストレートです。
二つ目は、「null や空 List の扱いをユーティリティ側で決めている」ことです。
呼び出し側は「とりあえず zip したい」とだけ書けばよく、毎回 null チェックをしなくて済みます。
三つ目は、「Collections.unmodifiableList で“結果を不変にして返している”」ことです。
zip の結果は“その時点のスナップショット”として扱うことが多いので、勝手に書き換えられないようにしておくと安全です。
Stream を使った zip 処理
IntStream.range でインデックスを流す
Stream で書くと、こうなります。
import java.util.List;
import java.util.stream.IntStream;
import java.util.stream.Stream;
public final class Zips {
private Zips() {}
public static <L, R> Stream<Pair<L, R>> stream(List<L> left, List<R> right) {
if (left == null || right == null) {
return Stream.empty();
}
int size = Math.min(left.size(), right.size());
if (size == 0) {
return Stream.empty();
}
return IntStream.range(0, size)
.mapToObj(i -> new Pair<>(left.get(i), right.get(i)));
}
}
Java使い方の例です。
Zips.stream(names, scores)
.map(p -> p.getLeft() + " : " + p.getRight())
.forEach(System.out::println);
Javaここでの重要ポイントは二つです。
一つ目は、「IntStream.range(0, size) で“インデックスの Stream”を作り、それを Pair に変換している」ことです。
インデックスを軸にして、2つの List を同時に参照しています。
二つ目は、「Stream.empty() を返すことで、“zip できない状況”を自然に表現している」ことです。
呼び出し側は、空 Stream ならそのまま何も処理されないので、余計な if 文が不要になります。
長さが違うとき、どう振る舞うかを決める
「短い方に合わせる」が基本だが、他の選択肢もある
今までの例では、「短い方の長さに合わせる」実装にしてきました。
これは多くの言語・ライブラリで採用されている、標準的な振る舞いです。
ただし、業務によっては別のルールが欲しくなることもあります。
短い方に合わせる(余りは捨てる)
長い方に合わせて、足りない側は null やデフォルト値で埋める
長さが違ったら例外にする
例えば、「必ず同じ件数であるべき」なら、こう書けます。
public static <L, R> List<Pair<L, R>> zipStrict(List<L> left, List<R> right) {
if (left == null || right == null) {
throw new IllegalArgumentException("null は許可しない");
}
if (left.size() != right.size()) {
throw new IllegalArgumentException("サイズが違います");
}
return zip(left, right); // さきほどの zip を再利用
}
Javaここでの重要ポイントは、
「“長さが違うときどうするか”を、ユーティリティのレベルで決めておく」ことです。
毎回呼び出し側で if 文を書くのではなく、zip(短い方に合わせる)、zipStrict(サイズ違いは例外)など、
“ポリシーの違い”をメソッド名で表現しておくと、コードの意図がとても読みやすくなります。
zip処理が役立つ具体例
例1:ヘッダ行+データ行を結びつける
CSV の1行目がヘッダ、2行目以降がデータ、というよくあるパターン。
List<String> header = List.of("id", "name", "score");
List<String> row = List.of("u001", "山田", "80");
Zips.stream(header, row)
.forEach(p -> System.out.println(p.getLeft() + " = " + p.getRight()));
// id = u001
// name = 山田
// score = 80
Javaここから Map<String, String> を作ることもできます。
import java.util.Map;
import java.util.stream.Collectors;
Map<String, String> record =
Zips.stream(header, row)
.collect(Collectors.toMap(
Pair::getLeft,
Pair::getRight
));
Java「ヘッダ名 → 値」という辞書が一発で作れるので、CSV処理などで非常によく使うパターンです。
例2:2つのバージョンを比較する
「旧データと新データを、同じ位置同士で比較したい」場合。
List<Integer> oldScores = List.of(70, 80, 90);
List<Integer> newScores = List.of(75, 82, 88);
Zips.stream(oldScores, newScores)
.forEach(p -> {
int oldScore = p.getLeft();
int newScore = p.getRight();
System.out.println(oldScore + " -> " + newScore);
});
Javaインデックスを意識せずに、「ペア」として扱えるのが zip の気持ちよさです。
まとめ:zipユーティリティで身につけてほしい感覚
zip処理は、
単に「2つの List を for 文で同時に回す」話ではなく、
「“位置で結びつく2つの列”を、きれいに扱う設計」です。
同じインデックスの要素同士をペアにしていく、という基本イメージを持つ。Pair<L, R> や Zips.zip のようなユーティリティで、「zip している」ことをコード上で明示する。
長さが違うときの振る舞い(短い方に合わせる/例外にする/埋める)を、メソッドとして切り分けておく。
CSVヘッダ+行、旧値+新値など、「2つの列をセットで扱いたい場面」で積極的に使う。
あなたのコードのどこかに、
2つの List を同じ for 文の中でゴリゴリ回している箇所があれば、
それを一度「zipユーティリティ+Pair」に置き換えられないか眺めてみてください。
その小さな整理が、
「複数の列を“関係のあるペア”として、気持ちよく扱えるエンジニア」への、
確かな一歩になります。
