Java入門|オブジェクトを配列で扱う方法

悟空やベジータをひとりずつではなく、まとめて管理する 配列で広がるオブジェクト操作

ここまでで、クラスからオブジェクトを作り、そのオブジェクトを変数で扱う方法を学んできました。たとえば悟空を表す変数、ベジータを表す変数、悟飯を表す変数というように、1人ずつ別々の変数で戦士を扱うことはできましたね。けれども、登場する戦士が増えてくると、それをひとつひとつ別の変数で管理するのはだんだん大変になってきます。

ドラゴンボールの世界で考えるなら、天下一武道会の出場者や、修行に集まった戦士たちをまとめて管理したくなる場面があります。そんなときに便利なのが、オブジェクトの配列です。これは、戦士オブジェクトをひとまとまりにして順番に扱える仕組みです。ここでは、オブジェクトを配列で扱うために必要な流れを、ドラゴンボールのたとえでやさしく整理していきます。

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

第7章では、int 型の配列を学びました。
たとえば、点数をまとめて扱うなら、次のような形でした。

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

これは、「int 型の値を5個分まとめて扱う場所を用意する」という意味でした。

オブジェクトでも考え方は同じです。
たとえばサイヤ人オブジェクトを3人分まとめて扱いたいなら、次のように書けます。

Saiyan[] warriors;
warriors = new Saiyan[3];

ここで大切なのは、int の代わりにクラス名 Saiyan を使っていることです。
つまり、オブジェクトも基本型と同じように、配列でまとめて扱えるのです。

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

ここがオブジェクト配列で特に重要なポイントです。

Saiyan[] warriors;
warriors = new Saiyan[3];

この2行を書いただけでは、「サイヤ人を3人作った」ことにはなりません。
できているのはあくまで、

  • Saiyan 型を入れるための配列
  • その要素を3つ分持つ箱

です。

ドラゴンボールで言えば、3人分の控え室を用意しただけで、まだ悟空もベジータも悟飯も入っていない状態です。

つまり、オブジェクトの配列では次の2段階が必要になります。

  1. 配列そのものを用意する
  2. 各要素に、実際のオブジェクトを代入する

この2つを区別しておくことが、とても大切です。

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

オブジェクトを配列で扱うには、次の流れで考えると整理しやすいです。

手順内容
1配列を準備する
2各要素にオブジェクトを作成して代入する

ドラゴンボールの世界に置きかえると、こうです。

手順ドラゴンボールでのイメージ
1戦士を並べるための控え席を用意する
2その席ごとに実際の戦士を配置する

この2段階がそろって、はじめて配列を通して戦士を扱えるようになります。

実際のプログラムで見てみる

では、本文の流れをドラゴンボールの世界に合わせて整理したサンプルプログラムを見てみましょう。

ファイル名:Sample9.java

class Saiyan
{
    private int power;
    private double energy;

    public Saiyan()
    {
        power = 0;
        energy = 0.0;
        System.out.println("戦士を作成しました。");
    }

    public void setSaiyan(int p, double e)
    {
        power = p;
        energy = e;
        System.out.println("戦闘力を" + power + "に気の量を" + energy + "にしました。");
    }

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

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

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

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

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

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

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

まずこの部分です。

Saiyan[] warriors;
warriors = new Saiyan[3];

ここでは、Saiyan 型の要素を3つ持つ配列を用意しています。
でも、まだここでは戦士オブジェクトそのものは作られていません。

この段階では、3つの箱があるだけだと考えるとわかりやすいです。

  • warriors[0] 用の箱
  • warriors[1] 用の箱
  • warriors[2] 用の箱

はあるけれど、中身はまだ空です。

ここで「もう3人作った」と思ってしまうと混乱しやすいので注意が必要です。

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

次に出てくるのがこのループです。

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

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

つまり、

  • warriors[0] に1人目の戦士
  • warriors[1] に2人目の戦士
  • warriors[2] に3人目の戦士

を入れているわけです。

ドラゴンボールで言えば、3つの控え席それぞれに、実際の戦士を1人ずつ座らせている状態です。

この2回目の new がないと、配列要素から戦士を扱うことはできません。
ここはオブジェクト配列で最も大事なポイントの1つです。

ループで3人分の戦士を一気に作れる

この書き方の便利なところは、繰り返し文を使ってまとめてオブジェクトを作れることです。

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

これは、実質的には次の3行と同じ意味です。

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

つまり、戦士が増えても同じパターンでまとめて初期化できるわけです。

人数が多くなるほど、この「まとめて扱える」ことの便利さがよく見えてきます。

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

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

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

ここでのポイントは、同じ Saiyan クラスから作られた3人でも、配列の各要素ごとに違う値を持てることです。

表で整理するとこうなります。

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

つまり、配列でまとめて扱っていても、それぞれのオブジェクトは独立した別々の戦士です。

ドラゴンボールでいえば、修行場に3人の戦士を並べていても、それぞれの強さや気は別々に持っている、ということです。

配列を使うと、まとめて表示もできる

最後に、もう一度ループを使って全員の情報を表示しています。

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

このループによって、

  • 1人目の戦士の状態
  • 2人目の戦士の状態
  • 3人目の戦士の状態

を順番に表示できます。

ここがオブジェクト配列の大きな強みです。
ひとりずつ別の変数で持っていると、show() の呼び出しも全部手で書く必要があります。
でも配列なら、繰り返し文と組み合わせてまとめて処理できます。

これは、戦士が増えていく場面で特に便利です。

実行結果から流れを確認する

この Sample9.java を実行すると、まず3回コンストラクタが呼ばれます。
つまり、戦士が3人作成されます。

そのあと、それぞれに戦闘力と気の量が設定され、最後に順番に表示されます。

流れとしてはこうです。

  1. 戦士を3人作成する
  2. 1人目に 9000 と 20.5 を設定する
  3. 2人目に 8500 と 30.5 を設定する
  4. 3人目に 7000 と 40.5 を設定する
  5. 全員の状態を順番に表示する

この流れを見ると、オブジェクト配列は「複数の戦士をひとまとまりにして操作する」仕組みだとよくわかります。

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

オブジェクト配列では、new が2回出てくるので混乱しやすいです。
でも役割は違います。

書き方役割
new Saiyan[3]配列を用意する
new Saiyan()実際のオブジェクトを作る

この違いをしっかり分けて考えることが大事です。

ドラゴンボールで言えば、

  • new Saiyan[3] は3人分の席を用意すること
  • new Saiyan() はその席に戦士を1人座らせること

です。

ここを理解しておくと、オブジェクト配列の仕組みがかなりすっきり見えてきます。

配列は便利だが制約もある

配列は、オブジェクトを順番にまとめて扱うにはとても便利です。
ただし、配列には「最初に要素数を決めておく必要がある」という特徴があります。

たとえば今回なら 3 人分と決めて作っています。

warriors = new Saiyan[3];

つまり、あとから自由に増やしたり減らしたりすることは得意ではありません。

だから、戦士の人数があらかじめ決まっている場面では使いやすいですが、状況によって数が増減するような場面では、別の仕組みが欲しくなります。
そのためにクラスライブラリにはコレクションが用意されていますが、ここではまず、配列でオブジェクトをまとめて扱えることをしっかり押さえるのが大切です。

この図では、まず左側に warriors という配列変数があります。
その先に warriors[0]、warriors[1]、warriors[2] という3つの配列要素が並んでいます。

それぞれの配列要素は、さらに別々の Saiyan オブジェクトを指しています。
これを見ると、配列の要素そのものが戦士なのではなく、「戦士を指す場所」になっていることがわかります。

また、3つのオブジェクトが別々の戦闘力と気の量を持っているので、同じクラスから作られていても、各オブジェクトが独立していることが分ります。

いちばん大事な感覚

「オブジェクトを配列で扱う方法」で大切なのは、次の感覚です。

  • オブジェクトも配列でまとめて扱える
  • まず配列を準備し、そのあとで各要素にオブジェクトを代入する必要がある
  • 配列を作っただけでは、まだオブジェクトは存在しない
  • 配列を使うと、複数のオブジェクトに対して同じ処理を順番に行いやすい
  • new 配列new オブジェクト は役割が違う

ドラゴンボールで言いかえるなら、

  • 戦士を並べる席をまず用意する
  • その席に1人ずつ戦士を配置する
  • 配列を使えば、複数の戦士をまとめて管理し、順番に表示したり設定したりできる

ということです。

この感覚がつかめると、オブジェクトを1人ずつ扱う段階から、複数のオブジェクトをまとめて扱う段階へ進めるようになります。これはJavaのプログラムを大きくしていくうえで、とても大切な一歩です。