「List<Person>の<Person>って何?」

前の記事で、 PersonオブジェクトをListに入れるコードを 紹介しました。

List<Person> persons =
    new ArrayList<>();

ここで気になるのが、

Listなのは分かる。
でも、なぜ<Person>が付いているの?

という部分です。

実はこの <Person> が、 「このListには何を入れるのか」 を表しています。

ジェネリクスは「扱う型を指定する」ための仕組み

まずは普通のListを考える

Javaでは、 複数のデータをまとめて扱いたいときに Listを使います。

List names =
    new ArrayList();

そして、データを追加できます。

names.add("田中");
names.add("佐藤");
names.add("鈴木");

これで、 複数の名前をListに入れられます。

ところが、 こんなこともできてしまいます。

names.add("田中");
names.add(30);

文字列の中に、 数値まで入っています。

ここで困る

このListには、 「文字列だけ入っている」と 思っていたのに、

実際には数値も入れられてしまう。

そこで登場するのが、 ジェネリクスです。

List<String>にすると何が変わる?

Listに <String> を付けてみます。

List<String> names =
    new ArrayList<>();

こうすると、 このListには Stringを入れる、 ということが明確になります。

names.add("田中");
names.add("佐藤");

これは問題ありません。

しかし、

names.add(30);

とすると、 コンパイル時にエラーになります。

List いろいろな型を入れられてしまう
List<String> Stringを扱うListだと明確になる

「何を入れるListなのか」をコード上で決められる。

<Person>ならPersonだけを扱う

前の記事で登場した Personを使ってみます。

class Person {

    String name;

}

このPersonをListに入れます。

List<Person> persons =
    new ArrayList<>();

すると、

Person person1 = new Person();
Person person2 = new Person();

persons.add(person1);
persons.add(person2);

のように、 Personオブジェクトを Listに入れられます。

一方で、

persons.add("田中");

のようにStringを入れることはできません。

イメージ

List<Person>

「Personを入れるList」

Person
├─ person1
└─ person2

これが、 List<Person> の基本的な考え方です。

StringやIntegerにも使える

ジェネリクスは、 Person専用の仕組みではありません。

例えば文字列なら、

List<String> names =
    new ArrayList<>();

数値なら、

List<Integer> numbers =
    new ArrayList<>();

と書けます。

List<String> 文字列を扱う
List<Integer> 整数を扱う
List<Person> Personを扱う

つまり、 Listという同じ仕組みを使いながら、 中で扱う型を変えることができます。

では「<T>」は何なのか?

ジェネリクスを調べていると、 今度はこんなコードを見ることがあります。

class Box<T> {

    T value;

}

急に T が出てきます。

Tって何?

最初は難しく見えますが、 それほど複雑ではありません。

Tは「あとで型を決めます」という目印

例えば、

Box<String>

とすれば、 Tの部分が Stringになります。

同じように、

Box<Integer>

とすれば、 Tの部分が Integerになります。

イメージ

Box<T>

Tはまだ決まっていない

Box<String>

T = String

Box<Integer>

T = Integer

自分でジェネリックなクラスを作ってみる

例えば、 何かを一つだけ保持する Boxを作ってみます。

class Box<T> {

    T value;

    void set(T value) {
        this.value = value;
    }

    T get() {
        return value;
    }

}

このBoxは、 何の型を入れるかを 最初には決めていません。

String用に使えば、

Box<String> box =
    new Box<>();

box.set("こんにちは");

Integer用に使えば、

Box<Integer> box =
    new Box<>();

box.set(100);

のように使えます。

一つのクラスを、 型だけ変えて使い回せるわけです。

「同じ仕組みを、いろいろな型で使いたい」

そんなときに ジェネリクスが役立ちます。

業務コードではどう見える?

実際のJava開発では、 自分でジェネリッククラスを作る場面よりも、 すでに用意されているクラスを見ることのほうが 多いかもしれません。

例えば、

List<User> users;

のようなコードです。

これを見たら、

コードを分解する

List

複数のデータを扱う

User

その中に入るデータの型

と考えれば、 かなり読みやすくなります。

さらに、

Map<String, Integer> scores;

というコードがあれば、

String キーの型
Integer 値の型

と読み取れます。

<>の中を見ると、そのデータ構造が何を扱うのか分かる。

「型を後から決める」と考える

ジェネリクスを理解するとき、 細かい仕様を全部覚えようとすると 少し難しく感じます。

最初は、

これくらいのイメージでOK

List<String>
→ Stringを扱うList

List<Person>
→ Personを扱うList

Box<T>
→ 型をあとから指定できるBox

と考えておけば十分です。

つまり、

「この仕組みは、どの型を扱うのか?」

という視点で <> の中を見るようにします。

これだけでも、 業務コードを読むときの負担がかなり減ります。

「<>」を見たら、まず中身を見る

業務コードを読んでいて、

List<Order> orders

のようなコードが出てきたら、 まず

「このListにはOrderが入るんだな」

と考えます。

例えば、

List<String> names
List<Person> persons
List<Order> orders

と並んでいたら、 それぞれ何を扱っているListなのかが すぐに分かります。

さらに、

Map<String, User> users

と書いてあれば、

読み方

String
→ キー

User
→ 値

というように、 <> の中身から データの関係を読み取れます。

Javaのコードが複雑に見えるときほど、 いきなり全体を理解しようとせず、

「<>の中には何が入っている?」

と確認してみると、 意外と整理しやすくなります。

今回のポイント

ジェネリクスは、 Javaでデータの型を安全に扱うために よく使われる仕組みです。

まずは、

覚えておきたい関係

List<String>
→ Stringを扱うList

List<Person>
→ Personを扱うList

Map<String, Integer>
→ StringとIntegerの組み合わせ

<T>
→ 型をあとから指定するための仕組み

くらいのイメージから始めれば十分です。

そして、Javaのコードで

「<>の中には何が入っている?」

と考えられるようになると、 ListMapを使ったコードが かなり読みやすくなってきます。

前の記事で学んだ

クラス → オブジェクト → List<Person>

という流れも、 ここでつながってきます。