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のコレクションフレームワーク、継承関係をちゃんと追ったことがなかった

0
Posted at

この記事を書いた理由

ListSetMap も、なんとなく「Javaでデータをまとめて扱うやつ」という認識のまま数年使ってきた。

ある日、コードレビューで「ここはListよりDequeのほうが意図が伝わりやすい」と指摘をもらった。そのとき「DequeってQueueと何が違うんでしたっけ」と聞き返してしまい、自分でも驚いた。使えてはいるが、整理されていない。そう気づいたので、改めて構造から確認することにした。


まず全体像を把握する

コレクションフレームワークは大きく2つのグループに分けられる。

java.util.Collection を頂点とするグループと、java.util.Map のグループだ。

MapCollection を継承していないため、厳密にはコレクションではない。ただし keySet()values()entrySet() という3つのコレクションビューを持っており、それを通じてコレクションとして扱うことができる。

Collection 配下のインタフェースはこのような構造になっている。

Iterable
  └── Collection
        ├── List
        ├── Set
        ├── Queue
        │     └── Deque

Collection 自体は Iterable を継承しているため、そのサブクラスはすべて拡張for文で反復処理できる。この継承関係を意識しておくと、「なぜこのクラスでfor-eachが使えるのか」が自然に理解できる。


各インタフェースの特徴と使い分け

List:順序とインデックス

インデックスで要素にアクセスできる順序付きリスト。実装クラスとしてよく使うのは ArrayListLinkedList

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

class Main {
    public static void main(String[] args) {
        List<Integer> list = new ArrayList<>();
        list.add(1);
        list.add(2);
        list.add(3);
        System.out.println(list);        // [1, 2, 3]
        System.out.println(list.get(1)); // 2

        for (Integer i : list) {
            System.out.println(i);
        }
    }
}

VectorStackList の実装クラスだが、現在は推奨されていない。スレッドセーフが必要な場面では Collections.synchronizedList、スタック操作が必要な場合は Deque を使うのが現在の主流のようだ。

Set:重複なし、順序なし

重複を許さないコレクション。順序は保持しない。HashSet はハッシュテーブルで要素を管理し、TreeSet は自然順序(昇順)で保持する。

内部実装が HashMapTreeMap に処理を委譲しているというのは知らなかった。SetMap が独立した別物に見えて、実は深いところで繋がっているのは少し意外だった。

Queue:FIFOの一方向キュー

先入れ先出し(FIFO)のデータ構造。offer() でエンキュー、poll() でデキュー、peek() で先頭の参照だけ行う。

import java.util.ArrayDeque;
import java.util.Queue;

class Main {
    public static void main(String[] args) {
        Queue<String> queue = new ArrayDeque<>();
        queue.offer("Alice");
        queue.offer("Bob");
        queue.offer("Charlie");

        System.out.println(queue.peek()); // "Alice"(削除しない)

        while (queue.peek() != null) {
            System.out.println(queue.poll()); // 順番に取り出す
        }
    }
}

LinkedListQueue の実装クラスだが、ArrayDeque のほうがパフォーマンスが良いとされている。

Deque:両端から操作できるキュー

Queue を継承したインタフェースで、先頭・末尾の両端から追加・削除ができる。FIFOにもLIFOにも使える柔軟さがある。

import java.util.ArrayDeque;
import java.util.Deque;

class Main {
    public static void main(String[] args) {
        Deque<String> deque = new ArrayDeque<>();
        deque.addFirst("Alice");
        deque.addLast("Bob");
        deque.addLast("Charlie");

        System.out.println(deque.peekFirst()); // "Alice"
        System.out.println(deque.peekLast());  // "Charlie"

        while (deque.peekFirst() != null) {
            System.out.println(deque.pollFirst());
        }
    }
}

スタックとして使いたい場面では Stack クラスではなく Deque が推奨されている理由は、Deque のほうが操作の表現が明示的で、パフォーマンスも優れているから。レビューで指摘を受けたのはまさにここだった。

余談だが、Deque の読み方を長らく「ディーキュー」と思っていたが、正しくは「デック」らしい。気にしないようにしていたが、口頭で出てくるたびに少し迷う。

Map:キーと値のペア

Collection を継承していないため独立した扱いになるが、コレクションフレームワークの一員ではある。

import java.util.HashMap;
import java.util.Map;

class Main {
    public static void main(String[] args) {
        Map<String, Integer> map = new HashMap<>();
        map.put("Alice", 100);
        map.put("Bob", 90);
        map.put("Charlie", 80);

        for (String key : map.keySet()) {
            System.out.println(key + ": " + map.get(key));
        }
    }
}

entrySet() を使うと、キーと値をまとめて取り出せる。keySet() でキーを取り出してから get() で値を引くより、entrySet() を使ったほうが内部的なアクセスが1回で済む分効率がよい。


理解が浅かった部分

QueueDeque の使い分けができていなかったのが一番の盲点だった。どちらも「キュー」という認識のまま、なんとなく LinkedList を使い続けていた。

DequeQueue を継承しているため Queue として使うこともできる。つまり「先入れ先出しだけでいいなら Queue、両端の操作が必要なら Deque」という判断が正しい。実装クラスとして ArrayDeque を選べば、どちらの用途にも対応できる。

また、MapCollection を継承していない点を意識せずにいたため、Collection のメソッドがそのまま使えると誤解していた時期があった。Mapiterator() が直接ないのはそのためで、反復処理したい場合は entrySet()keySet() を経由してIteratorを得る必要がある。


整理して気づいたこと

今回改めて確認できたのは、コレクションフレームワークは継承構造ごと把握すると迷いが減るという点だった。

どのクラスがどのインタフェースを実装しているかを知っておくと、「なぜこのクラスでfor-eachが使えるのか」「なぜこのメソッドが使えないのか」が構造から説明できるようになる。使えるかどうかを都度調べるより、継承関係を頭に入れておくほうが長い目で見て楽だと感じた。

TreeSetTreeMap の自然順序の仕組み、Comparator を使ったカスタムソートについてはまだ曖昧なところが残っているので、次の機会に整理したい。


この記事を書いた人について

株式会社Flexibilityでエンジニアをしています。
DX推進・システム開発を軸に、エンジニアが自律的に動ける環境を大事にしている会社です。

技術的に面白いことをやっていきたい方や、働き方に柔軟さを求めている方は、
よかったら一度のぞいてみてください。

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?