PR

Java:配列[array]とは?使い方の基本をわかりやすく3分で

Java

配列[Array]は、同じデータ型の複数の値を1つの変数に格納するためのデータ構造です。Javaにおける配列は固定長で、一度作成するとそのサイズを変更することはできないという特徴を持ちます。配列の各要素には、インデックス(添え字)を使ってアクセスし、インデックスは0から始まり、配列の長さ-1までの整数で表します。

java 配列
図1:Javaの配列

整数を格納するための配列を例にして考えると、配列を使うことで複数の整数値を1つの変数にまとめて管理することができ、個々の値にアクセスしたり、変更したりするのが簡単になります。

// 整数型の配列を宣言して初期化
int[] numbers = {1, 2, 3, 4, 5};

// 配列の要素にアクセス
System.out.println(numbers[0]); // 出力: 1
System.out.println(numbers[4]); // 出力: 5

↑は、numbersという配列に5つの整数値を格納。numbers[0]は配列の最初の要素(1)を、numbers[4]は配列の最後の要素(5)を示しています。

ページ前半では、配列の基本的な使い方について解説します。後半では「そもそも配列とは何か?」「配列はオブジェクトである!」というような動作原理の基本を解説しますので、是非最後までご覧ください。

スポンサーリンク

配列の宣言と初期化

Javaで配列を使うためには、①配列の宣言、②初期化というステップを踏む必要があります。ここでは、配列の宣言と初期化の基本的な方法についてご説明します。

配列の宣言

配列を宣言するというのは、配列の名前とそのデータ型をJavaに教えるということ。例えば、整数型の配列を宣言するには次のように書きます。

int[] numbers;

intデータ型を、[] は配列であることを示しており、numbers が配列の名前です。

配列の初期化

配列を宣言しただけでは、実際にデータを格納するためのメモリが割り当てられていません。データを格納するためには配列の初期化を行う必要があります。初期化にはいくつかの方法があります。

パターン1 サイズを指定して初期化
配列のサイズ(要素数)を指定して初期化する方法です。この方法では、配列の各要素はデフォルト値(整数の場合は0)で初期化されます。以下の方法で、5つの整数を格納できる配列 numbers が作成されます。サイズは必ず指定しないといけません(指定しない場合コンパイルエラーが発生します)。

// 配列の宣言
int[] numbers;

// 配列の初期化
numbers = new int[5];

パターン2 宣言と同時に初期化
配列を宣言すると同時に、初期化する方法もあります。この方法では、配列の各要素を具体的な値で初期化。以下の通りに記述すると、要素として1から5までの値を持つ配列 numbers が作成されます。

int[] numbers = {1, 2, 3, 4, 5};

配列の宣言&初期化自体は難しくありません。以下のコードだけ頭に入っていればOK。

// 配列の宣言
int[] scores;

// 配列の初期化
scores = new int[3]; // 3つの要素を持つ配列を初期化

// 宣言と同時に初期化
int[] ages = {18, 21, 25}; // 3つの値を持つ配列を作成
配列の宣言方法は実はいろんなパターンがある

Javaの配列宣言では、[] を型の後ろに書く方法と、変数名の後ろに書く方法の2種類があります。どちらでもコンパイルは通りますが、可読性やチームのコーディング規約によって推奨される方法が異なる場合があります。

型の後ろに [] を付ける例

int[] arrayA = new int[5];
String[] names = {"Alice", "Bob", "Carol"};

このように書くのが一般的で、配列であることが直感的にわかりやすいとされています。

変数名の後ろに [] を付ける例

int arrayB[] = new int[5];
String names2[] = {"Dave", "Ellen", "Frank"};

C言語由来の書き方で、Javaでも動作は同じですが、推奨されないことが多いです。

同じ行で複数の変数を宣言する場合の注意

int[] x, y;  // xもyもint配列
int x2[], y2; // x2はint配列、y2はint型の変数

1行で複数変数を宣言するとき、どこに [] を付けるかで配列なのか通常の変数なのかが変わるので注意が必要です。

まとめとして、[] は型の直後に書くのがわかりやすく、複数変数を1行で宣言するよりも1変数1行で宣言するほうが混乱を避けられます。

多次元配列

Javaでは、配列の中に配列を持つことができるため、複数の次元を持つ多次元配列を作成できます。最も一般的な多次元配列は二次元配列で、これは表やマトリックスのようなデータを扱うのに便利です。

二次元配列の宣言と初期化

二次元配列を宣言するには、次のように書きます。int はデータ型を、[][] は二次元配列であることを示しています。

int[][] matrix;

二次元配列についても初期化が必要です。考え方は通常の配列と同様です。

// 二次元配列の宣言
int[][] matrix;

// 二次元配列の初期化
matrix = new int[2][3]; // 2行3列の二次元配列を作成

// 宣言と同時に初期化
int[][] scores = {
    {80, 90, 100},
    {70, 85, 95}
};

多次元配列を使うことで、表形式のデータや複雑なデータ構造を効率的に管理できるようになります。例えば、表形式のデータ(成績表など)やグリッド状のデータ(画像のピクセル情報など)を扱う場合に便利です。

二次元配列だけでなく、三次元以上の配列も作成できますが、使用する際には配列の次元が増えるほど管理が複雑になるため、適切な使い分けが必要です。

配列の要素へのアクセス

続けて、作成した配列に対する操作系を諸々ご説明します。基本的にどのような操作でも共通して、配列内の各要素にアクセスするためにはインデックス(添え字)を使用する、というのが基本ルールです。

要素の取得

配列の要素を取得するには、配列名の後に角括弧 [] を使い、取得したい要素のインデックスを指定します。

int[] numbers = {10, 20, 30, 40, 50};
int firstElement = numbers[0]; // 最初の要素(10)
int thirdElement = numbers[2]; // 3番目の要素(30)

要素の設定

配列の要素を設定するには、同じく角括弧 [] を使って要素のインデックスを指定し、新しい値を代入します。

int[] numbers = {10, 20, 30, 40, 50};
numbers[1] = 25; // 2番目の要素を25に設定
numbers[4] = 55; // 5番目の要素を55に設定

二次元配列の要素へのアクセスと設定

二次元配列の要素にアクセスするには、行と列のインデックスを指定します。

// 宣言と同時に初期化
int[][] scores = {
    {80, 90, 100},
    {70, 85, 95}
};

// 要素へのアクセス
int firstScore = scores[0][0]; // 1行目の1列目の要素(80)
scores[1][2] = 99; // 2行目の3列目の要素を99に設定

配列の長さの取得

列の長さを取得するために length プロパティを使用します。配列の長さは、その配列に含まれる要素の数を示します。

int[] numbers = {10, 20, 30, 40, 50};
int length = numbers.length;
System.out.println("Array length: " + length); // 出力: Array length: 5
Q
二次元配列に対するlengthプロパティはどうなる?
A

「配列の配列」であり、lengthフィールドは最初の次元、つまり「行数」を返します。以下の例で確認してください。

public class TwoDimensionalArrayExample {
    public static void main(String[] args) {
        int[][] array = {
            {1, 2, 3},
            {4, 5, 6},
            {7, 8, 9}
        };

        // array.lengthは行数(外側の配列の長さ)を返す
        System.out.println("行数: " + array.length); // 3

        // 各行の列数(内側の配列の長さ)は個別に取得する必要がある
        System.out.println("1行目の列数: " + array[0].length); // 3
        System.out.println("2行目の列数: " + array[1].length); // 3
        System.out.println("3行目の列数: " + array[2].length); // 3
    }
}
  • 二次元配列の構造
    Javaでは、int[][]のような二次元配列は「配列の配列」として実装されます。つまり、外側の配列は複数の内側の配列を参照しています。
  • lengthフィールドの動作
    • array.lengthは外側の配列の要素数、つまり行数を返します。上記の例では3行なので3が返されます。
    • 各内側の配列は独立した配列であるため、行ごとの列数を取得するには、各行のlength(例:array[0].length)を参照します。
    • 注意点として、各行の列数は同じである必要はなく、異なるサイズの配列を保持することも可能です。

このように、二次元配列のlengthを直接指定すると、最初の次元のサイズ、すなわち行数が得られます。

配列のループ処理

配列の全ての要素にアクセスするために、forループや拡張forループ(foreachループ)を使います。

サンプルコード1 forループ

int[] numbers = {10, 20, 30, 40, 50};
System.out.println("Array elements (for loop):");
for (int i = 0; i < numbers.length; i++) {
    System.out.println("Element at index " + i + ": " + numbers[i]);
}
// 出力:
// Element at index 0: 10
// Element at index 1: 20
// Element at index 2: 30
// Element at index 3: 40
// Element at index 4: 50

サンプルコード2 拡張forループ

int[] numbers = {10, 20, 30, 40, 50};
System.out.println("Array elements (foreach loop):");
for (int number : numbers) {
    System.out.println("Element: " + number);
}
// 出力:
// Element: 10
// Element: 20
// Element: 30
// Element: 40
// Element: 50

参考 Javaのfor文:繰り返し処理の基本から応用までを3分で解説

配列のコピー

配列の内容を別の配列にコピーするには、Arrays.copyOf メソッドを使用します。このメソッドは、元の配列の内容を指定された長さの新しい配列にコピーします。

int[] numbers = {10, 20, 30, 40, 50};
int[] numbersCopy = Arrays.copyOf(numbers, numbers.length);
System.out.println("Copied array elements:");
for (int number : numbersCopy) {
    System.out.println("Element: " + number);
}
// 出力:
// Element: 10
// Element: 20
// Element: 30
// Element: 40
// Element: 50

参考 Java:メソッドの定義と呼び出しのコツを3分で解説

配列のソート

配列の要素を昇順に並べ替えるには、Arrays.sort メソッドを使用します。

int[] numbers = {50, 20, 40, 10, 30};
Arrays.sort(numbers);
System.out.println("Sorted array elements:");
for (int number : numbers) {
    System.out.println("Element: " + number);
}
// 出力:
// Element: 10
// Element: 20
// Element: 30
// Element: 40
// Element: 50

配列の検索

特定の要素が配列内に存在するかどうかを調べるには、Arrays.binarySearch メソッドを使用します。このメソッドを使用する前には、事前に配列をソートしておく必要があります。

int[] numbers = {10, 20, 30, 40, 50};
int index = Arrays.binarySearch(numbers, 30);
if (index >= 0) {
    System.out.println("Element found at index: " + index); // 出力: Element found at index: 2
} else {
    System.out.println("Element not found");
}

index = Arrays.binarySearch(numbers, 25);
if (index >= 0) {
    System.out.println("Element found at index: " + index);
} else {
    System.out.println("Element not found"); // 出力: Element not found
}

配列の長さの取得、配列のループ処理、配列のコピー、配列のソート、配列の検索について詳しく解説しました。これらの操作を理解することで、Javaの配列を効果的に利用できるようになります。

ここまでがJavaにおける配列の基本知識。ここからは、より実践に即した形でJavaの配列をより深く深堀して解説します。

Javaの「配列」をより本質的に理解する

そもそも配列とは何か?を改めて深堀します。

「配列」とは、同じ型の要素を連続的に並べて管理するための仕組みです。低レイヤー言語(C言語など)を学習済の方であれば、メモリ上に要素が連続して並んでいるイメージを持つことができるかと思います。一方、Javaでは配列自体がオブジェクトとして扱われ、要素の型や長さを保持します。

配列はオブジェクトである

配列はオブジェクトである――このフレーズは、初心者の方には少し抽象的に感じられるかもしれません。ここで、なぜ「ただのデータ型」ではなく「オブジェクト」であると言えるのか、その違いを平易な言葉で解説します。

配列にも「住所(参照)」がある

Javaにおいて、オブジェクトとは「newによってヒープ領域に作られた実体」を指します(参考 オブジェクトとは?)。クラスのインスタンスと同じように、配列も new int[5] のように書くと、Javaのヒープ領域に「配列オブジェクト」が確保され、そこへの参照(「住所」のようなもの)を変数が持つ仕組みです。

  • int x = 10; のようにプリミティブ型(int, doubleなど)を使うときは、その値を直接変数に格納します。
  • int[] arr = new int[5]; のように配列を使うときは、「配列の本体」はヒープ領域にあり、arr という変数は配列の本体がある「場所」を指し示すだけです。

「ただのデータ型」のように考えると、「配列変数に入っているのは配列そのもの」だと誤解しがちですが、実際には「配列そのものが置かれている場所への参照」が入っている点が大きな違いです。

length という情報を持っているという意味

配列はオブジェクトであるので、「要素数」や「要素の型」といった情報を内部に持つことができています。たとえば、arr.length という形で要素数を取得することができます。これは「配列オブジェクト」が自分が何個の要素を持っているのかを覚えているからです。

int[] arr = new int[5];
System.out.println(arr.length);  // 5 と出力

もし「配列」が本当にただの「数字のかたまり」や「型」だけであれば、こうしたメタ情報(要素数など)を持ち歩くことは難しいでしょう。オブジェクトとして扱われるからこそ、length という変数を備え、範囲外アクセスをチェックしたりできる仕組みが成り立っています。

instanceof 演算子や Object 型との関係

Javaのすべてのオブジェクトは、最終的に Object クラスを親に持つとされます。配列も例外ではなく、実は

int[] arr = new int[5];
System.out.println(arr instanceof Object); // true

のように、「配列は Object のインスタンス」として判定されます。これは、配列が特殊なクラスのインスタンスとして実装されていることを意味します。もし単なる「データ型」扱いであれば、instanceof Object が通ることはありません。

参考 インデックスの範囲チェック

Javaでは、配列のアクセス時に必ずインデックスが範囲内かどうかチェックされます。範囲外の場合(0 <= index < arr.length を外れる)は ArrayIndexOutOfBoundsException がスローされ、安全性が担保されています。

int[] arr = {1, 2, 3};
System.out.println(arr[3]);  // ArrayIndexOutOfBoundsException

参考 共変(Covariance)と型安全性

Javaの配列は「共変」という性質を持ち、例えば Number[] nums = new Integer[5]; のように、親クラス型の配列参照に子クラス型の配列オブジェクトを格納できます。しかし、実際の格納先は Integer[] なので、子クラスの要素以外を代入しようとすると実行時に ArrayStoreException が発生します。

Number[] nums = new Integer[5];
nums[0] = 10;      // IntegerなのでOK
nums[1] = 10.5;    // Doubleを代入しようとするとArrayStoreException

配列型変数 / 配列インスタンス / 要素 の区別を明確にする

最後に復習を兼ねて、配列型変数と配列インスタンス、要素の違いを改めて整理しておきましょう。

配列型変数

  • イメージ:
    配列型変数は、配列そのものを指し示す「ラベル」のようなものです。
  • 説明:
    • この変数は、配列そのもの(箱)の場所を記憶します。
    • 例えば、int[] scores; と書くと、scores という名前の箱を使う準備をしていることになりますが、まだ中身は作られていません。

配列インスタンス

  • イメージ:
    配列インスタンスは、実際に作られた「箱」です。
  • 説明:
    • new キーワードを使って実際の配列(箱)を作ります。
    • 例えば、scores = new int[5]; とすると、5個の数字を入れられる箱が作られ、その箱の場所が scores に記憶されます。

要素

  • イメージ:
    要素は、その箱の中に入っている「一つ一つの中身」です。
  • 説明:
    • 箱の中には、数字や文字などのデータが入ります。
    • たとえば、scores[0] は箱の中の最初の場所に入っている数字を意味し、scores[1] は2番目の場所の数字を指します。
// 1. 配列型変数の宣言(箱を使うためのラベルを作る)
int[] scores;

// 2. 配列インスタンスの生成(実際の箱を作る)
scores = new int[5];  // 数字を5個入れる箱が作られる

// 3. 要素へのアクセス(箱の中のそれぞれの場所に数字を入れる)
scores[0] = 90;  // 最初の場所に90を入れる
scores[1] = 80;  // 次の場所に80を入れる
// ... scores[2], scores[3], scores[4] にも必要な数字を入れる

このように、

  • 配列型変数 は箱の「ラベル」、
  • 配列インスタンス は実際に作られた「箱」、
  • 要素 はその箱の中に入る「一つ一つのデータ」と考えると、Javaの配列がどういう仕組みになっているかが分かりやすいと思います。

配列のまとめ

  • 配列はnewによって生成されるオブジェクト … 配列もクラスのインスタンスと同様に、ヒープ領域に配置され、その参照を変数が持ちます。
  • 要素数や型などを内部に保持length フィールドによる要素数の参照や、インデックスの境界チェックは、配列がオブジェクトとして情報を持っているから可能です。
  • コピーや参照のしくみがクラスのインスタンスと同じ … 配列変数に別の配列を代入すると「同じオブジェクト」を指すようになるなど、「ただのデータ型」とは扱いが異なります。

もし「配列はただのデータ型」と考えると、

  • 変数に格納されているのは「配列の中身」そのものだと思い込んでしまう
  • 配列を別変数に代入したら、配列が複製されたと思い込んでしまう
    などの誤解を招きやすいです。

しかし実際には、クラスのインスタンスと同じく、newによって作られた一つのまとまりを「アドレスを通じて参照している」状態です。こうした違いを理解すると、配列の挙動がより納得しやすくなるでしょう。

まとめ 配列の様々な記述パターン整理

以下は、Javaの配列宣言・初期化の様々なパターンを一次元配列と多次元配列に分けて整理したものです。資格試験対策として、各パターンの特徴と注意点を確認してください。

1. 一次元配列

1-1. 宣言のみ

配列の変数を宣言するだけの場合、宣言の仕方は主に2通りあります。

int[] arr;   // 一般的に推奨される書き方
int arr[];   // C言語風。どちらも有効ですが、可読性の観点から前者が好まれる

1-2. 宣言とサイズ指定(new演算子を利用)

配列のサイズが既に分かっている場合、new演算子を使って確保します。

int[] arr = new int[5];  // 要素数5の整数配列

1-3. 宣言と初期化子を使った同時初期化

要素が既に決まっている場合、初期化子を使って宣言と同時に初期化できます。

int[] arr = {1, 2, 3, 4, 5};

1-4. new演算子と初期化子の併用

明示的にnew演算子を使いつつ、初期値を指定する方法です。特にメソッドの引数として渡す場合など、配列リテラルだけでは使えない場面で有用です。

int[] arr = new int[]{1, 2, 3, 4, 5};

2. 多次元配列

多次元配列の場合も一次元配列と同様のパターンが存在しますが、次元数に応じた注意点があります。

2-1. 宣言のみ

【2次元配列の場合】

int[][] arr;    // 一般的な書き方
int arr[][];    // こちらも有効
int[] arr[];    // 混合形(あまり推奨されない)

3次元以上の場合も同様に書けます。

int[][][] arr;

2-2. 宣言とサイズ指定

【固定サイズの2次元配列】

int[][] arr = new int[3][4];  // 3行4列の2次元配列

【部分的なサイズ指定(ジャグ配列)】

int[][] arr = new int[3][];  // 行数3。各行の列数は後で設定
arr[0] = new int[2];
arr[1] = new int[4];
arr[2] = new int[3];

2-3. 宣言と初期化子による同時初期化

【直接初期化】

int[][] arr = {
    {1, 2, 3},
    {4, 5},
    {6, 7, 8, 9}
};

【new演算子と初期化子の併用】

int[][] arr = new int[][] {
    {1, 2, 3},
    {4, 5},
    {6, 7, 8, 9}
};

3. その他の注意点

  • 型と添字の関係:配列の宣言は基本的に「型+[]」または「型[]」の形ですが、配列変数の宣言の位置がコードの可読性に影響するため、型[] 変数名の形が推奨されます。
  • 初期化とnew演算子の使い分け:初期化子 {...} を用いる方法は、宣言時にのみ使用可能です。既に宣言済みの配列変数に再度初期化子だけで値を設定することはできません。その場合は、new演算子を併用する必要があります。
  • ジャグ配列:多次元配列は必ずしも全ての行が同じサイズである必要はなく、各行ごとに異なるサイズを持たせることができます。これをジャグ配列と呼び、メモリの無駄を省く場合や、データの不規則な構造を表現する場合に有用です。
  • オブジェクト型の配列:配列の要素はプリミティブ型だけでなく、クラス型も利用できます。例えば、String[] や MyClass[] のように宣言・初期化が可能です。

まとめ

Javaでの配列宣言と初期化には以下の主要なパターンがあります:

  • 【一次元配列】
    • 宣言のみ: int[] arr; または int arr[];
    • サイズ指定: int[] arr = new int[5];
    • 初期化子による同時初期化: int[] arr = {1,2,3,4,5};
    • new演算子と初期化子の併用: int[] arr = new int[]{1,2,3,4,5};
  • 【多次元配列】
    • 宣言のみ: int[][] arr;(その他の書き方も可能)
    • 固定サイズ: int[][] arr = new int[3][4];
    • 部分サイズ指定(ジャグ配列): int[][] arr = new int[3][];(後から各行を生成)
    • 初期化子による同時初期化:
      • 直接: int[][] arr = { {1,2,3}, {4,5}, {6,7,8,9} };
      • new演算子併用: int[][] arr = new int[][] { {1,2,3}, {4,5}, {6,7,8,9} };

これらのパターンを覚えておくと、Javaの配列操作において柔軟かつ適切なコードを書くことができ、資格試験の出題にも対応できるでしょう。

タイトルとURLをコピーしました