Java を使うなら覚えておいて欲しい書き方 - 配列・リスト・マップ

元ねた:Javaを使うなら必ず覚えておきたいデータ構造 - 配列・リスト・マップ - いろいろ解析日記
なんか色々と足りないよね、ってことで、色々と補足を。

配列

配列の生成

配列に含める要素が既に分かっている場合、例えば、

String[] strs = new String[2];
strs[0] = "hoge";
strs[1] = "piyo";

このような場合、以下のように記述できる。

String[] strs = { "hoge", "piyo" };

独自のクラスだったとしても、

Hoge[] hs = { new Hoge(), new Hoge() };

と、普通に可能。
これを使うと、引数を 2 つ受け取って配列にまとめて返すメソッドは、

Hoge[] toArray(Hoge a, Hoge b) {
    Hoge[] result = { a, b };
    return result;
}

と書ける。こんなメソッド書かない?説明用です。


ここで、

// コンパイルエラー
Hoge[] toArray(Hoge a, Hoge b) {
    return { a, b }
}

とはかけないので注意。
Java では、配列の初期化以外の位置で出てくる波括弧はブロックの始まりを表すからなんだと思う。
けど、式を期待する場所で出てきた波括弧はブロックの始まりなわけがないし、なんかもっとやりようはあったんじゃないかな、とか思う。
上のように書きたければ、配列の生成と分かるように、

Hoge[] toArray(Hoge a, Hoge b) {
    return new Hoge[] { a, b };
}

とすればいい。
この書き方はもちろん、配列の変数の初期化にも使える。

Hoge[] hs = new Hoge[] { a, b };

ま、おそらくこれのシンタックスシュガーとして

Hoge[] hs = { a, b };

があるんでしょうね。

配列からデータを取得する

元記事では、配列からデータを取得する方法として、

メソッド( 配列の名前[インデックス] );
変数 = 配列の名前[インデックス];
return 配列の名前[インデックス];
Javaを使うなら必ず覚えておきたいデータ構造 - 配列・リスト・マップ - いろいろ解析日記

としている。
これはつまり、

String[] strs = { "hoge" };
strs[0];

なんてのがコンパイルエラーになる、ってことが言いたいんだと思うけど、これは strs[0] ってのが文になれないってだけで、これ以外にも取得する方法はある。
例えば、

String first = "hoge, piyo".split(",")[0];

みたいに、メソッドの戻り値の配列から直接要素を取り出してみたり、

String[] strs = ...
String s = strs[0].substring(1);

みたいに、配列の要素そのものに用はなくて、直接メソッドやフィールドにアクセスしてみたりできる。

配列中の全てのデータに同じ処理をする

元記事ではインデックスベースの for 文のみしか出てきていないけど、配列の要素自体に変更を加えない場合で、かつ順番に処理を行う場合は、拡張 for 文が使用できる。

String[] strs = ...
for (String str : strs) {
    ...
}

これは、以下のループと等価と思って問題ない。

String[] strs = ...
for (int i = 0; i < strs.length; i++) {
    String str = strs[i];
    ...
}

もちろん、

for (String str : "hoge, piyo".split(",")) {
    System.out.println(str);
}

なんてのも可能。

可変長引数とか

メソッドに配列を渡すときのシンタックスシュガーとして使える可変長引数について。
可変長引数を使わない場合、

int max(int[] is) {
    ...
}
...
int m = max(new int[] { a, b, c });

なんて書かなければならなかった部分が、

int max(int...is) {
    ...
}
...
int m = max(a, b, c);

こんなにすっきり!
もちろん今までのように、可変長引数を受け取るようにしたメソッドに配列を new して渡すこともできるんだけど・・・やめましょう。


あと、あまり気にする必要はないんだけど、

void m(Object...objs) {}
...
m(null);    // objs == nullなのか、objs[0] == nullなのか、どっち?

こんなコード。これ、実は objs 全体が null になる。
なので、キャストとかで明示しておくのが吉。

m((Object) null);   // objs[0] == null
Object obj = null;
m(obj);             // objs[0] == null

でもまぁ、その、なんだ。null なんて渡すなよと (割と本気

リスト

元記事ではリストとして ArrayList しか紹介していないんだけど、まぁこれは (シングルスレッドしか考慮しないという前提があるとして) 正しい選択かな、と思う。
普通に使ってる分には LinkedList より ArrayList の方がほぼ全てのシチュエーションにおいて速いのがその理由。
途中への挿入に関しては LinkedList の途中への挿入の効率 - ぐるぐる〜 をどうぞ。

リストを生成する

一応、最大要素数が予測でき、かつその近くまで要素を詰めるつもりならば、キャパシティを指定するコンストラクタを指定した方がいい。

List<Hoge> ls = new ArrayList<Hoge>(10);

こんな感じ。
また、初期値があるならこんな記述も可能。

List<String> ls = new ArrayList<String>(5) {{
    add("hoge"); add("piyo"); add("foo"); add("bar");
}};

これは、無名クラスとインスタンスイニシャライザを組み合わせたイディオムで、広くは知られていない (気がする)。
無名クラスはいいだろう。その場でインターフェイスを実装したり、継承したりするために使用される。
インスタンスイニシャライザは、おそらく無名クラスがコンストラクタを持てないという制限を回避するのが元々の存在意義だと思われる。


また、このイディオムがあるため、汎用のコレクションクラスは非 final にしておくのが望ましい・・・かもしれない。
最悪の例として、Vector を継承した Stack というものがあるので、とても微妙ではあるのだが・・・
このイディオムをバッドノウハウの類として、使用しないという選択もあるだろう (その場合はコレクションクラスは final にするのが望ましい)。

リスト中の全てのデータに同じ処理をする

リストも配列と同様、拡張 for 文で使用することができる。

List<String> ls = ...
for (String str : ls) {
    ...
}

というか、Iterable を実装していれば拡張 for 文で使えるのであって、どちらかと言うと配列が特別扱いになっている。

マップ

マップの説明に HashMap しかでてこないのはどうなんだろう・・・
LinkedHashMap とか、TreeMap とかあるじゃん。使うじゃん。

マップ中の全てのデータに同じ処理をする

元記事ではわざわざキーの Set を取り出してから値を取り出しているけど、そんなことをせずに普通は Map.Entry を使用する。

for (Map.Entry<String, String> e : map.entrySet()) {
    String key = e.getKey();
    String data = e.getValue();
    
    System.out.printf("%s: %s%n", key, data);
}

で、元記事では map.keySet() が返すキーの順番が決まっていないとあるが、LinkedHashMap なら入れた順、TreeMap ならキーの大きさ順 (Comparator か Comparable によって指定される) の順番で取り出せるというのも覚えておいて損はない。