【Java入門】第15回 配列とコレクション|Javaで複数データをまとめて扱う
2024.07.05
以前の記事(第8回 変数とデータ型)では、配列を利用して複数個のデータをまとめる方法をご紹介しましたが、Javaでは配列以外にも、複数のデータを格納して操作することができるクラスが準備されています。当記事では、これらのクラスについて簡単にご説明します。
配列
おさらい
以前の記事(変数とデータ型)でも説明した通り、Javaの配列では同一の型の値・オブジェクトを、配列で複数個まとめて管理することが可能です。
配列は変数の宣言時に型または変数名の後ろに『 [ ] 』(大括弧)を付けることで利用できます。
int[] intArray01 = {0,1,2,3}; // 型の後ろに[]
int intArray02[] = {4,5,6,7,8,9}; // 変数名の後ろに[]
※参考 第8回 変数とデータ型(配列)
配列は、プリミティブ型(値)、クラス型(オブジェクト)のどちらでも利用できます。
クラス型の配列の場合、サブクラスも格納することが可能です。またインターフェイスの配列であれば、その実装クラスを格納することが可能です。
// クラス型(String型)の配列
String[] strArray = {"A", "B", "C"};
// interfaceの配列には実装クラスのオブジェクトを格納できる
List[] listArray = {new ArrayList<>(), new ArrayList<>()};
数値型のプリミティブ型の配列では、変数での代入と同じように、自身(配列)の型より範囲の小さい型の値であれば、格納することが可能です。但しこの場合、格納した時点で配列の型の値となってしまうため注意してください。
// 整数型で一番範囲の大きいlong型の配列を準備
long[] longArray = {0L, 1L, 2L, 3L};
int i = 1;
longArray[0] = i; // long は int より範囲が大きいので格納できる
char c = 0x0000;
longArray[2] = c; // long は char より範囲が大きいので格納できる
// これはコンパイルエラーになる
c = longArray[2]; // (char に long の値は代入できない)
Arraysクラス
Arraysクラスは、配列を操作するためのユーティリティクラスです。
配列の比較、ソート、検索、コピーなど、様々な操作のためのメソッドが準備されています。
ここでは、比較的使うことが多いと思われるメソッドをいくつか紹介します。
Arraysクラスの主なメソッド:
メソッド | 引数 | 説明 |
copyOf | ・対象の配列 ・長さ | 長さを指定して、対象の配列をコピーした配列を作成します。 |
equals | ・対象の配列1 ・対象の配列2 | 引数の2つの配列の各要素の値が、全て等しいかどうかを判定します。 |
fill | ・対象の配列 ・値 | 対象の配列のすべての要素に、引数で指定した値を設定します。 |
sort | ・対象の配列 | (数値型の配列の場合)値を昇順でソートします。 ※ソート範囲を指定したり、クラス型の配列を対象としてソートのルールを指定できるものもあります。 |
copyOf メソッド
配列の要素をコピーして、新しい配列を作成します。
新しく作成する配列は長さが指定でき、元の配列より小さい場合はその長さまで切り詰めた配列を、大きい場合は余った要素に初期値(プリミティブ型の場合はfalseまたは0、クラス型の場合はnull)を格納した配列を作成します。
String[] baseArray = {"A", "B", "C", "D", "E"};
// コピーして同じ長さの配列を作成
String[] sameArray = Arrays.copyOf(baseArray, baseArray.length); // {"A", "B", "C", "D", "E"}
// コピーして短い配列を作成
String[] shortArray = Arrays.copyOf(baseArray, 3); // {"A", "B", "C"}
// コピーして長い配列を作成
String[] longArray = Arrays.copyOf(baseArray, 7); // {"A", "B", "C", "D", "E", null, null}
equals メソッド
配列の変数同士を、配列オブジェクトのequalsメソッドで比較した場合、「==」演算子での比較と同じように、同じオブジェクト(参照)かどうかを判定しますが、Arrays.equalsメソッドで比較した場合は、配列の要素同士を比較して同一かどうかを判定してくれます。
// Arrays.equals は 要素を比較する(要素がnullでも比較できる)
String[] strArray1 = {"A", null, "B"};
String[] strArray2 = {"A", null, "B"};
System.out.println(strArray1.equals(strArray2)); // false(== で比較と同じ)
System.out.println(Arrays.equals(strArray1, strArray2)); // true
fill メソッド
配列のすべての要素に、指定した値を設定します。
何らかの処理で利用した配列を再度初期化する場合や、0以外の値を初期値とした配列を作成したい場合などに便利です。
int[] arr0 = new int[100]; // int配列の要素の初期値は全て0
// 何らかの処理
arr0[0] = 11;
arr0[1] = 12;
// (~中略~)
// 処理後に配列を再度初期化する
Arrays.fill(arr0, 0);
// 初期値が全て50、長さが100個の配列を作成する
// int[] arr50 = {50, 50, 50, 50, ... } と100個書かなくても良い
int[] arr50 = new int[100];
Arrays.fill(arr50, 50);
sort メソッド
(数値型の配列の場合)配列の要素を昇順にソートします。
文字列の場合は文字コード順で並びますが、辞書順ではないので注意してください。
(アルファベット大文字だけ、または小文字だけであれば辞書順に並びます)
// intの配列を昇順でソート
int[] sortArray = {15, 5, 8, 12, 2, 7};
Arrays.sort(sortArray); // sortArray は {2, 5, 7, 8, 12, 15} の並びになる
// 文字列の配列は文字コード順に並ぶ
String[] strSortArray = {"f", "A", "Z", "a", "c", "東", "京", "都"};
Arrays.sort(strSortArray ); // {"A", "Z", "a", "c", "f", "京", "東", "都"}
Arraysクラスには、他にも色々なメソッドが準備されています。
Java SEの各クラスにはドキュメントが提供されていますので、興味のある方は眺めてみても良いかもしれません。
※参考:Arrays クラス(Java APIドキュメント)
★あくまでJavaのドキュメントの紹介が目的ですので、プログラムを作成する上で、このドキュメントを全て理解する必要は全くありません。(必要な時に必要なところが理解できれば問題ありません)
コレクション
コレクションとは
Java では、複数の要素を格納可能なクラス群が準備されており、これらは総称してコレクションと呼ばれます。
コレクションのクラスでは、複数の要素を格納可能ですが、性質が異なるクラスごとにいくつかの系統(Set、List、Queue、Map)に分かれています。
コレクション・フレームワーク
Java SEでは、コレクション・フレームワークというコレクションを利用するためのプログラム群が準備されています。
(※参考 コレクション・フレームワークの概要 JavaSE21 & JDK21)
コレクション・フレームワークは、コレクションの型を定義するインターフェイスと、そのサブインターフェイスと実装クラス群、およびユーティリティクラスなどのサポートクラス群で構成されています。
(上記で紹介したArraysクラスも、コレクション・フレームワークのクラスの1つです)
Collectionとサブインターフェイス群
Collectionインターフェイス
Collectionは、要素を複数格納するコレクション群の核となるインターフェイスです。
Collectionの実装クラスは、Collectionのサブインターフェイスを経由して実装されており、これらはSet、List、Queueの3つの系統に分かれます。
主なサブインターフェイス:
サブインターフェイス | 特徴 |
Set | ・要素の重複を許可しない ・順序付けは持つ、持たないどちらもある(※) ・重複かどうかは(基本的に)格納する項目をequalsで比較(※) (※実装クラスの仕様に依存) |
List | ・順序付けを必ず持つ ・要素の重複も許可される(同じ参照を複数回格納することも可能) |
Queue・Deque | ・Queueは行列の意 ・順序付けで格納した要素を、先頭から取り出すことができる ・Deque はQueueのサブインターフェイス 「double ended queue」(両端キュー)の省略形 ・Dequeは先頭だけでなく、末尾からも要素を取り出すことができる |
ジェネリクスとは
Javaでは、クラス(コンストラクタ)やメソッドの引き受け可能な型を、クラスを利用する際に指定できるように、総称型(ジェネリクス)と呼ばれる型で作成することができます。
コレクションのクラスでは、このジェネリクスを用いた実装がなされており、コレクションを利用する際に、格納する要素の型を指定しておくことができます。利用する型は<>で囲んで指定します。
以下の例では、String型のオブジェクトのみ格納が可能なList を作成しています。
// Stringオブジェクトだけ格納可能なListを作成
List<String> strList1 = new ArrayList<String>();
// 変数の宣言側でジェネリクスを指定している場合は、インスタンス生成側では型を省略可能
List<String> strList2 = new ArrayList<>();
// var で変数宣言する場合は、インスタンス生成のほうで型を指定
var strList3 = new ArrayList<String>();
このように型を指定されたオブジェクトは、指定された型以外を要素として受け付けることができなくなります(コンパイルエラーになります)が、型を指定することで、プログラムの作成時に「どのような型の値が入っているのか」が明確になります。
基本的にコレクションのクラスを利用する場合は型を指定して利用するようにしましょう。
意図的に異なるクラスを同時に格納したい場合は、型を指定しない、またはObjectクラスを指定することで、すべてのクラスのインスタンスを格納可能になります。
// Object型を指定した場合は、全てのクラスのオブジェクトが格納可能なListになる
List<Object> anyList1 = new ArrayList<Object>();
// 型を指定しない場合も同様に、すべて格納可能になる
List anyList2 = new ArrayList<>();
var anyList3 = new ArrayList<>();
Setインターフェイス
Set(セット)は重複しない項目のコレクションです。
Setの主な実装クラスとしては、HashSet や TreeSet が挙げられます。
HashSet<E>クラス
HashSet クラスは、Set の最も基本となる実装クラスです。
値を格納した順序などの情報は保持されません。
HashSet は自身が格納していない要素のみ格納する(重複しない)ため、複数の要素から重複するものを除外するなどの利用ができます。
格納した値を利用する際は、繰り返し処理を行うための iterator(イテレータ)というオブジェクトを利用して値にアクセスします。(iteratorの利用方法については、次回の記事で説明します)
Set<String> hashSet = new HashSet<String>();
// Setに要素を追加(add)
hashSet.add("A");
hashSet.add("B");
hashSet.add("C");
hashSet.add("A"); // "A" は既にSetに含まれているので追加されない(上書きではない)
// サイズを確認(size)
System.out.println(hashSet.size()); // 3
Listインターフェイス
List(リスト)は順序を保持するコレクションです。
配列と同じように項目の位置(N番目)を指定して、要素の取得などの操作ができます。
ArrayList<E>クラス
ArrayListは最も利用頻度が高いListの実装クラスです。
要素の格納位置(index)とオブジェクトへの参照をセットで保持するため、同じ要素を複数回格納することも可能です。
配列と異なる点は、要素の数が可変であり、要素を追加する際に自動的にサイズを拡大するため、値の数が条件や操作によって異なるような場合は、まずArrayList の利用を考えます。
まず情報を全て集めてからまとめて処理を行いたい場合に、最も適したクラスと言えます。
List<String> strList = new ArrayList<String>();
// 要素を格納(add)
strList.add("A"); // 0 番目
strList.add("B"); // 1 番目
strList.add("C"); // 2 番目
strList.add("A"); // 3 番目 : 0番目の要素と同じ"A"を格納可能
strList.add("D"); // 4 番目
// 位置を指定して値を取得(get)
System.out.println(strList.get(0)); // "A"
System.out.println(strList.get(2)); // "C"
// 配列同様、範囲外や不正なインデックスでのアクセスは実行時エラー
System.out.println(strList.get(10)); // 実行時エラー(範囲外のインデックス)
System.out.println(strList.get(-1)); // 実行時エラー(不正なインデックス)
// 位置を指定した置き換え(set)
strList.set(2, "Z"); // 2 番目を"Z"に置き換え
// 位置を指定して削除(remove)
strList.remove(3); // 3 番目("A")を削除
// 要素が含まれているかを確認(contains)
System.out.println(strList.contains("A")); // true
// 要素の順番を逆転させた(ひっくり返した)新しいListを作成(reversed)
List reverseList = strList.reversed();
// リストをクリアして空にする(clear)
strList.clear();
簡易なリストの利用(List.of/Arrays.asListメソッド)
Listには、簡易に生成して利用可能なメソッドが List インターフェイス または Arrays クラスに準備されています。(これらのメソッドは、List の簡単な動作確認をしたい場合や、配列をList に変換して処理したい場合などに重宝しますので、あわせて紹介しておきます)
下記の例では、List.of および Arrays.asList メソッドを利用して、引数で指定した項目のListを生成しています。これらのメソッドで生成したList のインスタンスは、要素の変更(追加、削除、差し替えなど)が行えないList になることに注意してください。
// List.of メソッドを利用したListの生成
List<String> ofList1 = List.of("A", "B", "C");
// 配列からListへの変換
String[] strArray = {"X", "Y", "Z"};
List<String> ofList2 = List.of(strArray);
// Arrays.asList メソッドを利用したListの生成
List<Integer> asList1 = Arrays.asList(1, 2, 3);
// 配列からListへの変換
Integer[] intArray = {7, 8, 9};
List<Integer> asList2 = Arrays.asList(intArray);
// これらのメソッドで生成したListは、どちらも要素の変更ができない
ofList1.add("D"); // 実行時エラー(要素の追加)
asList2.remove(1); // 実行時エラー(要素の削除)
Queue・Dequeインターフェイス
Queue(キュー)は英単語で「列」(人などが並んでいる列)の意味のとおり、格納する要素を順序のとおり並べて保持します。列の並び方は一方通行で、取り出すことができるのは列の先頭の要素のみになります。
(Queueは先頭からしか取り出せない)
※基本的に実装クラスはFIFO(First IN First OUT =最初に入ったものが最初に出る、の意。「ファイフォ」と読みます)になりますが、要素の追加時に並び替えの順番(優先順)を指定できるようなクラスも有ります。
Deque(デック)は「double ended queue(両端キュー)」の省略で、queue が一方通行だったのに対し、その名のとおり両端(先頭・末尾)から、双方向に要素の追加や取り出しが可能です。
(Dequeは先頭からも末尾からも取り出せる)
ArrayDequeクラス
ArrayDequeはQueueまたはDequeの一般的なクラスです。
(双方向にアクセス可能であれば、一方向にだけ使うこともできるので、Queueを使いたい場合もArrayDequeを使うのが一般的です)
下記のサンプルでは、末尾/先頭から要素を格納しています。
// Deque
Deque<String> strDeque = new ArrayDeque<String>();
// 末尾に格納(offer または offerLast)
strDeque.offer("DEF");
strDeque.offerLast("GHI");
strDeque.offer("JKL");
// 先頭側から格納(offerFirst)
strDeque.offerFirst("ABC");
Dequeでは、取り出す場合も先頭/末尾どちらからでも取り出せます。
// 先頭を取得+先頭から削除(pollFirst)
System.out.println(strDeque.pollFirst()); // ABC
// 末尾を取得+最後尾から削除(pollLast)
System.out.println(strDeque.pollLast()); // JKL
Mapインターフェイス
Mapインターフェイス
Mapは、キー(key)と値(value)を1セットで格納するコレクションです。
キーと値の型は、両方ともジェネリクスで指定することができます。
格納した値は、格納時に指定したキーによって取り出すことが可能です。
また、既に登録されているキーを指定して別の値を格納した場合は、値を上書きして保存されます。
上書きを防止したい場合、キーが登録済かどうかを先に確認することも可能です。
HashMap<K, V>クラス
HashMapは最も利用頻度が高いMapの実装クラスです。
値を格納した順序は保持されないため、キーで値を取得します。また、キーや値のソートなどの機能は持ちませんが、その分処理が早いクラスとなっています。
// 野菜名(String)をキーに、価格(Integer)を保持するHashMapの例
Map<String, Integer> priceMap = new HashMap<String, Integer>();
// キーと値をMapに登録(同じキーの項目があった場合は上書き)
priceMap.put("ダイコン", 198);
priceMap.put("キャベツ", 248);
// キー「キャベツ」を指定してpriceMapから値を取得
System.out.println(priceMap.get("キャベツ")); // 248
// 既にキーが登録されているかどうかを取得
boolean hasKey;
hasKey = priceMap.containsKey("ダイコン"); // true
hasKey = priceMap.containsKey("ニンジン"); // false
// ダイコンの値段を変更(198 -> 178)
priceMap.put("ダイコン", 178);
System.out.println(priceMap.get("ダイコン")); // 178
Collectionsクラス
配列に対する「Arrays」クラスと同じように、コレクション群の様々な操作をサポートするためのユーティリティクラスである「Collections」というクラスも準備されています。
※参考 Collections クラス(Java API ドキュメント)
入門編では難易度が高いものが多いため、ここでは比較的簡単に利用できるソートの利用例を紹介します。
List<String> strSortList = new ArrayList<String>();
// 要素を格納(add)
strSortList.add("f");
strSortList.add("A");
strSortList.add("Z");
strSortList.add("a");
strSortList.add("c");
strSortList.add("東");
strSortList.add("京");
strSortList.add("都");
// 自然順序付け(数値なら値の昇順、文字列なら文字コード順)でソート
Collections.sort(strSortList);
// ※StringのListは標準出力で値を確認可能
System.out.println(strSortList); // [A, Z, a, c, f, 京, 東, 都]
他にも、複数のデータを持つクラスのListをルールを指定してソートしたり、Listの中に指定した別のListと同じ並びの部分があるか、など、面白い処理もあるのですが、これらは別の機会があれば、またご紹介したいと思います。