配列の基本

配列は値の集合で、同じデータ型の値をまとめて扱うことができるもの。加えてJavaの配列はオブジェクトでもある。

執筆時バージョン
Java

Java SE 8

配列とは

int[] scores = { 30, 80, 40 };

System.out.println("scores[0]:" + scores[0]);
System.out.println("scores[1]:" + scores[1]);
System.out.println("scores[2]:" + scores[2]);
出力
scores[0]:30
scores[1]:80
scores[2]:40

配列は値の集合で、同じデータ型の値をまとめて扱うことができるもの。配列の概念自体は他のプログラミング言語(CやRuby)でもだいたいある。

変数だけでは値を1つしか保持できない。プログラムにおいては大量のデータをさばくことがが求められるので、1つしか値を保持できない変数だけでは困ってしまう。例えばテストの点を管理するアプリケーションの場合、生徒一人ずつに変数を割り当てるのかってことになってしまう。よって値をまとめて扱うことができる集合の概念が必要となり、配列やコレクションが提供されている。

配列は変数と同様に名前をつける。そして個々の値にはインデックス(添字)をつけてアクセスする。よってどのインデックスに値が入っているのかが識別できるものになるので重要。インデックスは0から始まりn-1までとなるのも忘れていけない。

配列の主な特徴。

  • ブラケット([])で配列を宣言したり、インデックスを指定して値にアクセスする

  • インデックス(添字)は0から始まりn-1までとなる

  • サイズは1度生成すると変えられない

Javaの配列の主な特徴。

  • 配列がオブジェクトであること(後述)

    • length(フィールド)からサイズを取得できる

    • 配列で宣言した変数にnullを代入することが可能

  • 配列操作用のユーティリティクラスとしてjava.util.Arraysがある

できればコレクションを使う

値の集合という意味であれば配列の他にもコレクションがあり、基本的にはコレクションの方を使うようにする。インデックスでアクセスできる集合という意味であればjava.util.Listでも十分対応できる。コレクションの方が可能な操作は多いし、配列よりはるかに手広いし安全。またJavaの配列では微妙な点(共変であるなど)があるので、やっぱりコレクションを優先して使うように心がける。積極的に使うのは数値計算の場合など。

配列の宣言、生成と初期化、アクセス、イテレーション

宣言

型とブランケット([])で宣言する。

int[] scores;
String[] messages;

次のように変数名の方にブラケットをつける記述方法でも可能だがJavaでは型+[]が普通。変数名に[]をつけてしまうと[]の部分が名前なのか何なのかよくわからなくなる。素直に型+[]にした方がいい。

あまりやらない
int scores[];

生成と初期化

2つの方法がある。

  • 配列生成式

  • 配列初期化子

new 型名[要素数]と書くのが配列生成式。

配列生成式の例
int[] scores = new int[5];

この例であれば、要素数5で値が全て0のint型配列が生成される。要素数は指定された数で確定し、値としては(フィールドなどと同様に)デフォルト値が設定される。この段階で既に値は設定されていることに注意する。

一方、{}を使って値を指定するのが配列初期化子。

(省略した)配列初期化子の例
int[] scores = {30,80,40,20,70};

この例であれば、要素数5で値が30,80,40,20,70のint型配列が生成される。値は後から変更できるが要素数はこの数で変更できなくなるので、要素数の間違えは注意する。

上の書き方は次の書き方の省略したもの。

配列初期化子の例
int[] scores = new int[]{30,80,40,20,70};

アクセス

配列の変数に対して、[]内にインデックス(添字)を指定することで値にアクセスできる。存在しないインデックスにアクセスしようとするとArrayIndexOutOfBoundsExceptionが発生する。

int[] scores = new int[] { 30, 80, 40, 20, 70 };
System.out.println("1番目の値は" + scores[0] + "です。");

イテレーション

for文で全要素にアクセスする場合は、こんなかんじ。lengthフィールドが要素数を保持しているので、i=0からi < lengthまでで全てのインデックスを現わすことができる。

int[] scores = new int[] { 30, 80, 40, 20, 70 };
for (int i = 0; i < scores.length; i++) {
  System.out.println(scores[i]);
}

インデックスを操作する必要が無いのであれば、拡張for文の方がインデックス用の変数を持たなくていいのでスッキリする。

int[] scores = new int[] { 30, 80, 40, 20, 70 };
for (int score : scores) {
  System.out.println(score);
}

Streamももちろん生成可能。Arrays#stream()を利用する。

int[] scores = new int[] { 30, 80, 40, 20, 70 };
Arrays.stream(scores)
      .forEach(System.out::println);

多次元配列

配列の配列という形で、2次元でも3次元でも作成できる。

Javaにおける配列

オブジェクトであること

Javaでは配列がオブジェクトでもあるため、Object型で定義されているメソッドなどが利用できる。加えて、lengthフィールドとclone()メソッドが利用できる。

ただObject型にキャストできたりもしてしまうので微妙。また、配列で宣言した変数にnullを代入することが可能。

lengthフィールド

要素数が保持されたフィールド。配列は生成してしまえば要素数を変えられないので、lengthフィールドも変わらない(finalとなっている)。インデックスを用いたfor文でのアクセスなどで利用する。

String#length()のようにメソッドじゃないのかと勘違いする人がいるが、配列の場合はあくまでもフィールド。String#length()はStringに定義されているメソッドで名前がlengthでかぶっているだけ。

通常オブジェクト指向におけるコーディングではフィールドよりもメソッドに依存した方がよいのだが、配列のlengthに関しては特別(なかんじで)許されている感がある。

共変であること

これは「EFFECTIVE JAVA 第2版」P.116に詳しく書かれているが、次のコードがコンパイルエラーにならない。

「EFFECTIVE JAVA 第2版」P.116より引用
Object[] objectArray = new Long[1];
objectArray[0] = "I don't fit in";

実行時にjava.lang.ArrayStoreExceptionになるので実行するまでわからない。やっかい。やっぱりListを使う。

Appendix B: 改訂履歴

  • v1.0, 2017-04-20: 初稿