配列リテラル完全ガイド:言語別の書き方・挙動・落とし穴とベストプラクティス

配列リテラルとは — 概要

配列リテラル(array literal)とは、ソースコード中に配列(または配列に相当するデータ構造)を直接記述して初期化する構文のことを指します。言語ごとに表記や意味合いは異なりますが、要は「その場で配列を作るための簡便な記法」です。たとえば JavaScript の [1, 2, 3]、Python の [1, 2, 3]、C の {1, 2, 3} といった書き方が該当します。

基本的な例(言語別)

  • JavaScript:

    配列リテラルは角括弧で表現します。例: [1, 2, 3]。スプレッド構文 [...a, 4] や、要素の省略(エリジョン)も可能です。

  • Python:

    リストリテラルは角括弧で表現します。例: [1, 2, 3]。タプルは括弧/カンマで表現されます(例: (1, 2) または 1, 2)。

  • Java:

    宣言時は波括弧で初期化できます: int[] a = {1, 2, 3};。宣言と同時でない場合は new int[]{1,2,3} と記述します。

  • C / C++:

    C: int a[] = {1,2,3};。C++11 以降は統一初期化(brace-init)として std::vector v = {1,2,3}; のように使えます。

  • PHP:

    近年は短縮表記で [1, 2, 3]。古い書き方は array(1,2,3)

  • Go:

    複合リテラルで []int{1,2,3} のように記述します。

  • Ruby:

    角括弧で [1,2,3]、または %w などの補助表記があります。

配列リテラルの役割と利点

  • 可読性: 何が入っているかを直感的に示せるため、コードの理解が速くなります。

  • 簡潔さ: 明示的に配列オブジェクトを生成するコンストラクタ呼び出しや繰り返し処理を省けます。

  • 初期化の一貫性: 定数的な配列やテストデータを手早く作れるため、テストやサンプルで重宝します。

言語ごとの詳細な違い(重要ポイント)

  • 型と静的/動的:

    静的型付け言語(Java、C、C++、Go、C#など)ではリテラルは要素型を満たす必要があります。動的型付け言語(JavaScript、Python、Rubyなど)では異なる型を混在させた配列も許容されます。

  • 構文制約:

    Java では宣言と同時でないと短縮リテラル(波括弧のみ)を使えません。C/C++ の初期化は集合初期化(aggregate initialization)規則に従います。JSON は配列リテラルを持ちますが、末尾のカンマは許可されません(厳しい構文)。

  • ミューテーション(可変性):

    ほとんどの言語でリテラルから生成された配列は可変(要素の追加・変更が可能)ですが、言語によっては不変型やイミュータブルな配列を使うこともできます(例: 一部ライブラリや言語機能で提供)。

  • 配列の中身の評価タイミング:

    リテラル中に式が入る場合、その式の評価はリテラルが評価される時点で行われます。注意点として、Python の「デフォルト引数にリストリテラルを使う」ようなパターンは、デフォルト値が関数定義時に一度だけ評価されるため副作用に注意が必要です(mutable default argument 問題)。

  • スパース(まばら)配列と「穴」:

    JavaScript では要素を省略して [1, , 3] のように書くと「空きスロット(hole)」ができ、forEach や map がそのスロットを飛ばす挙動になります(アクセスすると undefined が返ることが多いが、存在しないスロットと undefined の扱いは微妙に異なります)。

  • トレイリングカンマ:

    多くの言語(JavaScript、Python、Ruby、C/C++(一部)など)は配列リテラルの末尾に余分なカンマを許すが、JSON や古い言語仕様では禁止されることがあるため注意。

実行時の振る舞い — 参照とコピー

配列リテラルが作るのは通常「新しい配列オブジェクト」です。したがってリテラルを式の中で複数回書くと毎回別の配列が生成されます。ただし言語実装や最適化によってはコンパイラが定数配列を共有するケースがあるため、参照の等価性(== や ===、is など)には注意が必要です。

また配列の中身がオブジェクトである場合、配列自体は新規に生成されても要素として格納されるのは元のオブジェクトへの参照であり、深いコピーにはならない(いわゆる「浅いコピー」)ことがほとんどです。深いコピーが必要なら言語固有の方法やライブラリを使う必要があります。

よくある落とし穴と注意点

  • Python の mutable default:

    def f(a=[]): ... のようにデフォルト引数に空のリストリテラルを書くと、そのリストは関数定義時に一度だけ生成され、呼び出し間で共有されます。意図しない副作用の原因になります。

  • JavaScript の「穴」や sparse 配列:

    省略した要素は「スロットが存在しない」状態になり、map/forEach はそのスロットを飛ばします。配列の長さ(length)は穴を含む位置で増えるため、期待しない挙動に注意。

  • JSON と言語のリテラルの違い:

    JavaScript の配列リテラルと JSON の配列表現は似ていますが、JSON はより厳密でありコメントや末尾カンマが許されません。API 間でデータをやり取りする際は JSON 仕様に従う必要があります。

  • 型の狭義化(narrowing)や暗黙変換:

    C++ の list-initialization は狭義化(例: 浮動小数点を整数に落とすなど)を禁止する場合があるため、初期化時の型変換に注意。

  • パフォーマンス面:

    短くて小さなリテラルを多用する場合は可読性が上がりますが、巨大なリテラルをソースに直書きするとコンパイル時やロード時にコストがかかることがあります。また、実行時に何度も新しい配列を生成すると GC(ガベージコレクション)負荷が高まる可能性があります。

実用的なベストプラクティス

  • 言語仕様を把握する: トレイリングカンマ、スパース配列、評価タイミングなど言語ごとの仕様を理解しておく。

  • ミュータブルなデフォルトは避ける: Python の関数デフォルトなど、初期化時に一意のオブジェクトを生成するパターンを使う(例: def f(a=None): if a is None: a = [])。

  • 定数として扱う場合はイミュータブルにする: 変更不可にしたい配列は言語の不変コレクションや readonly 指定を検討する(例: Java の List.of、C# の IReadOnlyList など)。

  • 深いコピーが必要なら明確に行う: 浅いコピー(配列リテラルやスプレッド)では要素内オブジェクトの参照が共有されるため、必要ならシリアライズや専用ライブラリで深いコピーを行う。

  • JSON と混同しない: クライアント・サーバ間で配列データをやり取りする際は JSON の制約に従い、言語側のリテラルはシリアライズして送受信する。

まとめ

配列リテラルはコード上で配列を簡潔に作れる強力な構文であり、可読性や記述量の面で大きな利点があります。一方で言語ごとの振る舞い(型、評価タイミング、スパース要素、トレイリングカンマの可否など)は微妙に異なり、思わぬバグや性能問題の原因になります。実務では各言語の仕様を理解した上で、イミュータブル性やコピーの挙動、デフォルト値の取り扱いに注意して使うことが重要です。

参考文献