Java超|オブジェクト配列のしくみ

戦士を1人ずつ管理する段階から、チーム全体をまとめて扱う段階へ。オブジェクト配列を理解すると、複数のオブジェクトを順番に生成し、設定し、表示できるようになります。

ここまでで、クラスからオブジェクトを作り、そのオブジェクトを変数で扱う方法を学んできました。

たとえば、SaiyanWarrior クラスから1人の戦士オブジェクトを作り、その戦士の戦闘力や気の力を設定することができました。

しかし、実際のプログラムでは、1人だけを扱えばよい場面ばかりではありません。

ドラゴンボールの世界観で考えると、任務に参加する戦士が1人だけとは限りません。

悟空、ベジータ、悟飯のように、複数の戦士をまとめて管理したい場面があります。

1人目は goku。
2人目は vegeta。
3人目は gohan。

というように、すべて別々の変数で管理することもできます。

しかし、人数が増えてくると、変数名も増え、同じ処理を何度も書くことになり、だんだん扱いにくくなります。

そこで便利なのが、オブジェクト配列です。

オブジェクト配列を使うと、複数のオブジェクトを1つの配列にまとめて管理できます。
さらに、for 文と組み合わせれば、全員の状態を順番に作成したり、設定したり、表示したりできます。

ドラゴンボール風にたとえると、オブジェクト配列は、サイヤ人戦士を並べる控え席です。

ただし、ここで大切なのは、席を用意しただけでは戦士本人はまだ存在しないという点です。

まず席を用意する。
そのあとで、各席に戦士を1人ずつ配置する。

この2段階をしっかり分けて考えることが、オブジェクト配列を理解する一番大事なポイントです。この記事では、オブジェクト配列のしくみを、配列の準備、各要素へのオブジェクト生成、for 文による一括処理という流れで整理していきます。

配列はオブジェクトもまとめて扱える

配列というと、最初は int 型の点数や double 型の数値をまとめて扱うイメージが強いかもしれません。

たとえば、戦闘力スコアを5個まとめて扱うなら、次のような形です。

int[] scores;
scores = new int[5];

これは、int 型の値を5個分まとめて入れられる配列を用意する書き方です。

同じように、オブジェクトも配列でまとめて扱えます。

たとえば、SaiyanWarrior オブジェクトを3人分まとめて扱いたい場合は、次のように書けます。

SaiyanWarrior[] warriors;
warriors = new SaiyanWarrior[3];

int の代わりに、クラス名 SaiyanWarrior を使っているところがポイントです。

配列の種類書き方扱うもの
int 型の配列int[] scoresint 型の値
SaiyanWarrior 型の配列SaiyanWarrior[] warriorsSaiyanWarrior オブジェクトへの参照

ここで少し注意が必要です。

SaiyanWarrior[] warriors は、SaiyanWarrior オブジェクトそのものを直接ぎっしり並べるというより、SaiyanWarrior オブジェクトを指す参照を並べる配列です。

ドラゴンボール風にたとえると、戦士本人を箱の中に詰め込むのではありません。

戦士を指す登録札を、0番、1番、2番のように順番に並べて管理するイメージです。

配列を作っただけでは、まだ戦士は存在しない

オブジェクト配列で特に重要なのが、この点です。

SaiyanWarrior[] warriors;
warriors = new SaiyanWarrior[3];

この2行を書いただけでは、戦士オブジェクトが3人作られたわけではありません。

ここで作られているのは、SaiyanWarrior 型の参照を3つ入れられる配列です。

つまり、3人分の控え席を用意しただけです。

書き方起きていること
SaiyanWarrior[] warriors;配列変数を宣言する
new SaiyanWarrior[3]SaiyanWarrior 型の参照を3つ入れられる配列を作る
new SaiyanWarrior()実際の戦士オブジェクトを1人作る

ドラゴンボール風にたとえると、new SaiyanWarrior[3] は、3人分の控え席を用意することです。

でも、その席にはまだ誰も座っていません。

つまり、配列の各要素はまだ空の状態です。

この状態で、いきなり次のように書こうとすると問題が起きます。

warriors[0].show();

なぜなら、warriors[0] はまだ実際の SaiyanWarrior オブジェクトを指していないからです。

席はある。
でも戦士はいない。

この感覚をしっかり持つことが大切です。

オブジェクト配列では2つのnewを区別する

オブジェクト配列では、new が2種類出てきます。

ここがとても混乱しやすいところです。

書き方役割ドラゴンボール風のたとえ
new SaiyanWarrior[3]配列を作る3人分の控え席を用意する
new SaiyanWarrior()オブジェクトを作る実際の戦士を1人登場させる

同じ new でも、作っているものが違います。

new SaiyanWarrior[3] は、配列を作っています。
new SaiyanWarrior() は、戦士オブジェクトを作っています。

この2つがそろって、はじめてオブジェクト配列として使える状態になります。

図:配列を作っただけではオブジェクトはまだない

この図が示していること

この図では、new SaiyanWarrior[3] によって配列が作られた直後の状態を表しています。

warriors[0]、warriors[1]、warriors[2] という3つの要素はあります。
しかし、それぞれの要素にはまだ SaiyanWarrior オブジェクトが入っていません。

ここから分かるのは、オブジェクト配列では、配列を作ることと、オブジェクトを作ることは別の作業だということです。

配列を作っただけでは、戦士を3人作ったことにはなりません。

オブジェクト配列で必要な2つの作業

オブジェクトを配列で扱うには、次の2つの作業が必要です。

手順内容ドラゴンボール風のイメージ
1配列を準備する戦士を並べる控え席を用意する
2各要素にオブジェクトを作って代入する各席に実際の戦士を座らせる

この2つを分けて考えると、オブジェクト配列はかなり分かりやすくなります。

配列を作っただけでは、各要素はまだオブジェクトを指していません。

各要素に new SaiyanWarrior() を代入して、はじめてその要素からメソッドを呼べるようになります。

オブジェクト配列をSample3.javaで確認する

ここからは、具体的なプログラムで確認していきます。

ファイル名:Sample3.java

class SaiyanWarrior
{
    private int battlePower;
    private double kiPower;

    public SaiyanWarrior()
    {
        battlePower = 0;
        kiPower = 0.0;
        System.out.println("戦士を作成しました。");
    }

    public void setWarrior(int b, double k)
    {
        battlePower = b;
        kiPower = k;
        System.out.println("戦闘力を" + battlePower + "に、気の力を" + kiPower + "にしました。");
    }

    public void show()
    {
        System.out.println("戦闘力は" + battlePower + "です。");
        System.out.println("気の力は" + kiPower + "です。");
    }
}

class Sample3
{
    public static void main(String[] args)
    {
        SaiyanWarrior[] warriors;
        warriors = new SaiyanWarrior[3];

        for(int i = 0; i < warriors.length; i++){
            warriors[i] = new SaiyanWarrior();
        }

        warriors[0].setWarrior(9000, 20.5);
        warriors[1].setWarrior(8500, 30.5);
        warriors[2].setWarrior(7000, 40.5);

        for(int i = 0; i < warriors.length; i++){
            warriors[i].show();
        }
    }
}

このプログラムでは、SaiyanWarrior オブジェクトを3人分まとめて扱っています。

1人ずつ別々の変数を用意するのではなく、warriors という配列にまとめています。

配列要素役割
warriors[0]1人目の戦士を指す
warriors[1]2人目の戦士を指す
warriors[2]3人目の戦士を指す

配列を使うことで、複数の戦士を順番に扱いやすくなります。

最初のnewは配列を作っている

まず注目するのは、次の部分です。

SaiyanWarrior[] warriors;
warriors = new SaiyanWarrior[3];

ここでは、SaiyanWarrior 型の配列を用意しています。

ただし、この時点では戦士オブジェクトはまだ作られていません。

この段階で用意されているのは、次の3つの要素です。

要素状態
warriors[0]まだ戦士を指していない
warriors[1]まだ戦士を指していない
warriors[2]まだ戦士を指していない

ドラゴンボール風にたとえると、3つの控え席が用意されただけです。

まだ悟空も、ベジータも、悟飯も座っていません。

この状態では、warriors[0] から setWarrior() や show() を呼び出すことはできません。

参照先の戦士がまだいないからです。

2回目のnewで実際の戦士を作る

次に出てくるのが、この for 文です。

for(int i = 0; i < warriors.length; i++){
    warriors[i] = new SaiyanWarrior();
}

ここで初めて、各配列要素に SaiyanWarrior オブジェクトを作って代入しています。

つまり、次の3行をまとめて書いているのと同じ考え方です。

warriors[0] = new SaiyanWarrior();
warriors[1] = new SaiyanWarrior();
warriors[2] = new SaiyanWarrior();

この処理によって、各要素が実際の戦士オブジェクトを指すようになります。

処理起きていること
warriors[0] = new SaiyanWarrior();1人目の戦士を作って、0番に入れる
warriors[1] = new SaiyanWarrior();2人目の戦士を作って、1番に入れる
warriors[2] = new SaiyanWarrior();3人目の戦士を作って、2番に入れる

ドラゴンボール風にたとえると、用意した3つの控え席に、実際の戦士を1人ずつ座らせている状態です。

ここまで来て、ようやく次のように、各戦士に対してメソッドを呼び出せるようになります。

warriors[0].setWarrior(9000, 20.5);

for文でまとめてオブジェクトを作れる

このプログラムでは、for 文を使って3人分の戦士をまとめて作っています。

for(int i = 0; i < warriors.length; i++){
    warriors[i] = new SaiyanWarrior();
}

ここで使っている warriors.length は、配列の要素数を表します。

今回の配列は new SaiyanWarrior[3] で作っているため、warriors.length は 3 です。

i の値実行される処理
0warriors[0] = new SaiyanWarrior();
1warriors[1] = new SaiyanWarrior();
2warriors[2] = new SaiyanWarrior();

このように、配列と for 文を組み合わせると、同じパターンの処理をまとめて書けます。

もし戦士が10人、20人と増えても、for 文の形は大きく変わりません。

人数分だけ同じ処理を繰り返せるのが、配列の強みです。

図:各要素にオブジェクトを作って代入する

この図が示していること

この図では、warriors[0]、warriors[1]、warriors[2] の各要素が、それぞれ別の SaiyanWarrior オブジェクトを指している状態を表しています。

warriors[0] は、戦闘力 9000、気の力 20.5 の戦士を指しています。
warriors[1] は、戦闘力 8500、気の力 30.5 の戦士を指しています。
warriors[2] は、戦闘力 7000、気の力 40.5 の戦士を指しています。

ここから分かるのは、配列でまとめて管理していても、各要素が指すオブジェクトは別々に存在し、それぞれ独自の状態を持てるということです。

配列要素ごとに別々の状態を持てる

次に、それぞれの戦士に異なる値を設定しています。

warriors[0].setWarrior(9000, 20.5);
warriors[1].setWarrior(8500, 30.5);
warriors[2].setWarrior(7000, 40.5);

ここでのポイントは、同じ SaiyanWarrior クラスから作られた3人でも、それぞれ別々の状態を持てることです。

配列要素戦闘力気の力
warriors[0]900020.5
warriors[1]850030.5
warriors[2]700040.5

ドラゴンボール風にたとえると、同じサイヤ人戦士の設計図から生まれた戦士でも、戦闘力や気の力はそれぞれ違います。

配列でまとめて管理していても、各要素が指しているオブジェクトは別々です。

そのため、1人目、2人目、3人目で異なる値を持たせられます。

配列要素からメソッドを呼び出す

オブジェクト配列では、配列要素を通してメソッドを呼び出せます。

warriors[0].setWarrior(9000, 20.5);
warriors[0].show();

これは、warriors[0] が指している SaiyanWarrior オブジェクトに対して、setWarrior() や show() を呼び出しているという意味です。

書き方意味
warriors[0].setWarrior(...)0番の要素が指す戦士に値を設定する
warriors[1].show()1番の要素が指す戦士の状態を表示する
warriors[i].show()i番の要素が指す戦士の状態を表示する

ドラゴンボール風にたとえると、0番席の戦士に訓練値を設定したり、1番席の戦士に状態報告をさせたりしているようなものです。

ただし、ここでも大切なのは、配列要素がオブジェクトを指している必要があるという点です。

まだ new SaiyanWarrior() を代入していない要素からメソッドを呼ぶことはできません。

for文で全員の情報を表示する

最後に、もう一度 for 文を使って、全員の情報を順番に表示しています。

for(int i = 0; i < warriors.length; i++){
    warriors[i].show();
}

このループによって、次のように処理が進みます。

i の値実行される処理
0warriors[0].show();
1warriors[1].show();
2warriors[2].show();

これにより、1人目、2人目、3人目の状態を順番に表示できます。

もし配列を使わなければ、次のように1つずつ書く必要があります。

warriors[0].show();
warriors[1].show();
warriors[2].show();

3人ならまだよいですが、10人、30人と増えていくと大変です。

配列と for 文を使えば、人数が増えても同じ形で処理できます。

ドラゴンボール風にたとえると、控え席に並んだ戦士へ順番に声をかけ、状態報告をしてもらう流れです。

実行の流れを整理する

Sample3.java の処理全体を流れで整理すると、次のようになります。

順番処理内容
1SaiyanWarrior[] warriors;配列変数を宣言する
2new SaiyanWarrior[3]3人分の参照を入れる配列を作る
3for 文で new SaiyanWarrior()各要素に戦士オブジェクトを作る
4setWarrior()各戦士に異なる値を設定する
5for 文で show()全員の状態を順番に表示する

実行すると、まずコンストラクタが3回呼び出されます。

つまり、戦士が3人作られます。

そのあと、各戦士に戦闘力と気の力が設定され、最後に全員の状態が順番に表示されます。

この流れを見ると、オブジェクト配列は、複数のオブジェクトをまとめて作り、まとめて扱うための仕組みだと分かります。

図:for文で全員を順番に操作する

この図が示していること

この図では、for 文を使って warriors[0]、warriors[1]、warriors[2] を順番に操作する流れを表しています。

i が 0 のときは warriors[0].show() が実行されます。
i が 1 のときは warriors[1].show() が実行されます。
i が 2 のときは warriors[2].show() が実行されます。

ここから分かるのは、オブジェクト配列と for 文を組み合わせると、複数のオブジェクトに対して同じ処理を順番に実行できるということです。

1人ずつ別々に show() を書くよりも、配列とループを使うほうが、人数が増えたときにも対応しやすくなります。

オブジェクト配列で特に注意したいこと

オブジェクト配列では、次の点に注意すると理解しやすくなります。

注意点内容
配列を作っただけではオブジェクトはないnew SaiyanWarrior[3] は席を作るだけ
各要素に new が必要warriors[i] = new SaiyanWarrior(); で戦士を作る
配列要素は参照を持つオブジェクト本体ではなく、オブジェクトへの参照
参照先がない要素からメソッドは呼べないオブジェクトを代入してから使う
length を使うと要素数分だけ処理できるfor 文と相性がよい

特に大切なのは、new SaiyanWarrior[3] と new SaiyanWarrior() の違いです。

この2つを同じものとして見てしまうと、オブジェクト配列で混乱しやすくなります。

ドラゴンボール風にたとえるなら、

new SaiyanWarrior[3] は3人分の控え席を用意すること。
new SaiyanWarrior() はその席に座る戦士を1人作ること。

この違いです。

配列は便利だが要素数はあとから簡単には変えられない

配列は、複数のオブジェクトを順番に扱うときにとても便利です。

ただし、配列には大切な特徴があります。

最初に要素数を決めて作る必要があるという点です。

今回なら、次のように3人分として作っています。

warriors = new SaiyanWarrior[3];

この配列は、基本的に3つの要素を持つ配列です。

あとから自由に4人目、5人目を追加していくのは得意ではありません。

仕組み特徴
配列要素数を最初に決める
コレクション要素数の増減に対応しやすい

ここでは、まず配列でオブジェクトをまとめて扱う考え方をしっかり押さえることが大切です。

人数があらかじめ決まっている場合には、配列はとても分かりやすく使えます。

ドラゴンボール風にたとえると、3人任務と決まっているなら3席の控え席を用意すれば十分です。

でも、任務の途中で参加人数がどんどん増えるような場面では、別の管理方法がほしくなります。

オブジェクト配列を使うと何が便利なのか

オブジェクト配列の便利さは、複数のオブジェクトをまとめて同じ処理にかけられるところです。

やりたいこと配列を使う利点
複数の戦士を作るfor 文でまとめて作れる
複数の戦士を表示するfor 文で順番に show() を呼べる
順番に管理するwarriors[0]、warriors[1] のように番号で扱える
人数が増えても対応しやすい変数名を大量に増やさなくてよい
同じ処理を繰り返せるループと組み合わせやすい

1人ずつ別々の変数で扱う場合は、人数が増えるほどコードが長くなります。

SaiyanWarrior goku;
SaiyanWarrior vegeta;
SaiyanWarrior gohan;

しかし配列なら、1つの変数 warriors でまとめて扱えます。

SaiyanWarrior[] warriors = new SaiyanWarrior[3];

ドラゴンボール風にたとえると、戦士を1人ずつ別々の巻物で管理するのではなく、チーム名簿としてまとめて管理するようなものです。

ここで押さえておきたい重要ポイント

最後に、オブジェクト配列で大切なポイントを整理します。

ポイント内容
オブジェクトも配列でまとめて扱えるクラス名[] の形で配列を作れる
配列を作っただけではオブジェクトはないnew SaiyanWarrior[3] は参照の箱を作るだけ
各要素にオブジェクトを代入する必要があるnew SaiyanWarrior() を各要素に入れる
配列要素はオブジェクトへの参照を持つ要素そのものがオブジェクト本体ではない
for 文と相性がよい作成、表示などをまとめて行える
new 配列 と new オブジェクト は役割が違う席を作る処理と、戦士を作る処理は別

ドラゴンボール風にたとえると、オブジェクト配列は戦士を並べる控え席です。

まず、任務に参加する戦士を並べる席を用意します。
そのあとで、各席に実際の戦士を1人ずつ配置します。
そして、配列番号を使って、1人目、2人目、3人目を順番に扱います。

この感覚がつかめると、オブジェクトを1人ずつ扱う段階から、複数のオブジェクトをまとめて管理する段階へ進めます。

オブジェクト配列は、Javaのプログラムを少し大きくしていくうえで、とても大切な一歩です。