Java入門:配列とコレクションのよくある間違い

レッスンの本文で注意している点を、つまずきの側から46件ぶん並べ直しました。書いた覚えのある形が見つかったら、 そのままレッスンに戻って前後を読めます。

配列の基本

レッスンを読む

1 始まりと勘違いする

数学やスプレッドシートに慣れていると、最初の要素を arr[1] と書きたくなります。Java のインデックスは 0 始まりなので、最初は arr[0]、最後は arr[arr.length - 1] です。arr[arr.length] と書くと off-by-one エラー で ArrayIndexOutOfBoundsException になります

配列の基本で詳しく読む

length をメソッド呼び出しで書く

arr.length() と書きたくなりますが、配列の length は プロパティ なのでカッコは付けません。Strings.length() (カッコあり) との違いが地味に厄介で、コンパイラに cannot find symbol と怒られたら大抵これです

配列の基本で詳しく読む

null チェックを忘れる

int[] arr = null; の状態で arr.lengtharr[0] を触ると、即 NullPointerException で落ちます。引数で受け取る配列は、信頼できないなら if (arr == null) のチェックを先に入れる癖をつけましょう

配列の基本で詳しく読む

配列の要素にアクセス

レッスンを読む

負のインデックスを渡す

一部の言語 (例えば Python) では arr[-1] で末尾要素が取れますが、Java では 負のインデックスはすべて例外。便利機能はないので、自分で arr.length - 1 を書く必要がある

配列の要素にアクセスで詳しく読む

配列を for で走査

レッスンを読む

i < arr.lengthi <= arr.length にしてしまう

配列の添字は 0 から arr.length - 1 までです。i <= arr.length だと最後の周で arr[arr.length] という存在しない要素にアクセスし、ArrayIndexOutOfBoundsException で落ちます。継続条件は必ず <、と決めてしまうのが安全です

配列を for で走査で詳しく読む

末尾に余計な , が残る

result = result + arr[i] + ", "; のように書くと、最後の要素の後ろにもカンマと空白が付いてしまい、"1, 2, 3, " のような出力になります。テストはこれを fail と判定します。先頭だけ特別扱い (if (i > 0) で前に ", ") で組み立てるのが定番のテクニックです

配列を for で走査で詳しく読む

空配列の処理を忘れる

arr.length0 の場合に何を返すかを考えていないと、NullPointerException や思わぬ文字列を返してしまうことがあります。今回の課題では空配列は "" を返す決まりです。for (int i = 0; i < arr.length; i++) パターンなら自然にループをスキップしてくれるので、安心して使えます

配列を for で走査で詳しく読む

Javaで配列の合計・平均を求める方法(forループ)

レッスンを読む

null チェックを書きすぎる / 書かなさすぎる

今回の課題では arrnull で渡されることは想定していませんが、実務だと null が来る可能性があります。安全側に倒すなら if (arr == null) return 0; を冒頭に書きます。逆に「null ではなく空配列で渡す」と決めたなら、書く必要はありません。呼び出し側との契約をはっきりさせる ことが、null を巡るバグを防ぐ一番のコツです

Javaで配列の合計・平均を求める方法(forループ)で詳しく読む

配列の平均

レッスンを読む

整数除算のままにしてしまう

return sum / arr.length; と書くと戻り値の型が double でも、右辺の int / int の結果 intdouble に変換されるだけです。arrayAvg(new int[]{4, 7})5.5 を期待しているのに 5.0 が返ってきて「あれ?」となります。割り算の前に、片方を (double) でキャスト しましょう

配列の平均で詳しく読む

キャストの位置を間違える

(double)(sum / arr.length) は意味がありません。括弧の中で先に int の割り算が走ってしまうからです。(double) sum / arr.length のように、キャストは割り算の左側の値だけに掛けるのが正解です

配列の平均で詳しく読む

空配列を素通りさせる

if (arr.length == 0) return 0.0; を忘れると、arr.length0 のとき sum / 0 を計算しようとして ArithmeticExceptionNaN が出てきます。割る前に必ず分母が 0 でないかをチェックするのが鉄則です

配列の平均で詳しく読む

配列の最大値

レッスンを読む

max = 0 で初期化して負の数で失敗

int max = 0; で始めると、配列が [-1, -5, -3] のように 全要素がマイナス のとき、答えが 0 になってしまいます。実際の最大値は -1 なのに、です。max の初期値は arr[0] または Integer.MIN_VALUE にするのが鉄則です

配列の最大値で詳しく読む

空配列で IndexOutOfBoundsException

int max = arr[0]; を書いた直後に、空配列 [] が渡されると、arr[0] にアクセスした瞬間に例外が飛びます。必ず if (arr.length == 0) return 0; のような 空配列ガード を先頭に置いてください

配列の最大値で詳しく読む

>=> の違い

更新条件を arr[i] >= max と書いても答え自体は同じになりますが、>= だと「同じ値」に出会うたびに上書きが走るので、わずかに余計な代入が発生します。> で「より大きいときだけ更新する」のが教科書的な書き方です

配列の最大値で詳しく読む

配列の線形探索

レッスンを読む

見つかった後もループを続けてしまう

for の中で return i; ではなく、int found = i; のように変数に保存するだけにしてしまうと、後ろにある同じ値で found が上書きされてしまうことがあります。「先頭から見て 最初 に見つかった位置」という仕様なので、見つけた瞬間に return で抜けるのが正解です。どうしてもループの後で返したい場合は、見つかった時点で break; してから return found; する形にしましょう

配列の線形探索で詳しく読む

index の初期化忘れ / 初期化ミス

拡張 for で書くときに int index = 0; の宣言を忘れると cannot find symbol のコンパイルエラーになります。逆に int index = -1; から始めてしまうと、arr[0]target と一致したときに -1 が返ってしまい、テストが fail します。初期値は 0 が正解です

配列の線形探索で詳しく読む

複数一致の挙動を取り違える

arr = [10, 20, 10, 20]target = 10 の答えは、0 (最初の 10) であって 2 (後ろの 10) ではありません。普通の for ループで先頭から return する書き方になっていれば自然に 0 が返りますが、「最後に一致した index を返したい」と仕様を勘違いすると 2 を返すコードを書いてしまいます。仕様を読むときに「最初」「最後」「全部」のどれを期待されているか必ず確認するクセを付けましょう

配列の線形探索で詳しく読む

拡張 for ループ

レッスンを読む

for-each で要素を書き換えたつもりになる

for (int x : arr) { x = ... } は元の配列に反映されない。書き換えたいときは通常の for 文を使い、arr[i] に直接代入する

拡張 for ループで詳しく読む

コロン : の代わりにカンマやセミコロンを使う

通常の for 文の影響で for (int x ; arr)for (int x , arr) と書いてしまう。正しいのは コロン : 一文字 で、それ以外は全部コンパイルエラー

拡張 for ループで詳しく読む

要素の型を間違える

int[] の配列に対して for (String x : arr) とやると incompatible types のエラーになる。要素の型と一致させる必要があり、自信がなければ var (Java 10 以降) でコンパイラに推論させるのも手

拡張 for ループで詳しく読む

ArrayList を使う

レッスンを読む

import を忘れる

ArrayList を使うには import java.util.ArrayList; が必要です。これを忘れると cannot find symbol というエラーが出ます。IDE なら自動補完でやってくれますが、手書きの試験などでは注意しましょう

ArrayList を使うで詳しく読む

要素アクセスで list[0] と書いてしまう

ArrayList はクラスなので、配列の [] 記法は使えません。必ず list.get(0) のようにメソッド呼び出しで取り出します。逆に、取り出した値の型 (Integer) を int 変数に入れるときは int x = list.get(0); のように自動で変換されます (オートアンボクシング)

ArrayList を使うで詳しく読む

raw 型で書いてキャストが必要になる

ArrayList list = new ArrayList(); のようにジェネリクスを省くと、get(0) の戻り値は Object になります。Integer x = (Integer) list.get(0); のように明示的なキャストが必要になり、安全性も下がります。型は必ず <> で指定しましょう

ArrayList を使うで詳しく読む

List を走査する

レッスンを読む

list.size と書いてしまう

要素数は list.size() です。配列の arr.length はカッコなしのフィールドですが、ArrayListsize() は メソッド なので必ず () が必要です。list.size と書くとコンパイルエラーになります

List を走査するで詳しく読む

intInteger を取り違える

ArrayList<Integer> には基本型の int をそのまま入れているように見えますが、実はラッパクラス Integer に自動変換 (auto-boxing) されています。list.remove(5) と書くと「インデックス 5 番目を削除」、list.remove(Integer.valueOf(5)) と書くと「値が 5 の要素を削除」になり、結果がまったく違うので注意してください

List を走査するで詳しく読む

走査中に remove する

上で書いた ConcurrentModificationException の話です。拡張 for ループ中に list.remove(...) を呼ぶと例外が飛びます。安全に消すなら Iterator.remove() を使うか、別 List に集めてからまとめて削除します

List を走査するで詳しく読む

要素の削除と検索

レッスンを読む

remove(int)remove(Object) の混同

特に List<Integer> で頻発します。位置で消すのか、値で消すのかを意識し、値で消したい場合は Integer.valueOf(...) で包みましょう

要素の削除と検索で詳しく読む

null を渡したときの挙動

contains(null)remove(null) は実行時例外にはならず、null を要素として比較します。null を入れないリストを作りたいなら、追加前に if (value != null) でガードします

要素の削除と検索で詳しく読む

拡張 for 文の中で remove を呼ぶ

for (String s : list) { list.remove(s); } のように書くと ConcurrentModificationException で落ちます。走査中に消したいときは Iterator.remove を使うか、list.removeIf(s -> 条件) のような書き方に切り替えてください

要素の削除と検索で詳しく読む

HashMap の基本

レッスンを読む

null をキーに使ってしまう

HashMap は技術的には null キーを 1 つだけ許容しますが、可読性が落ちるうえ ConcurrentHashMap では NPE を投げます。実務では null キーは使わないと決めておきましょう

HashMap の基本で詳しく読む

auto-boxing を忘れて型エラー

Map<String, Integer>intput するときは自動で Integer に箱詰め (auto-boxing) されますが、逆に int x = map.get("apple") のように受け取るとき、もし null が返ると NPE になります。Integer x = ... で受けるか getOrDefault でデフォルト値を指定するのが安全です

HashMap の基本で詳しく読む

順序が保たれていると勘違いする

HashMapput した順番を保持しません。順番に処理したいときは LinkedHashMap を使うか、キーをリスト化してソートしてから回します

HashMap の基本で詳しく読む

Map を走査する

レッスンを読む

HashMap の順序を期待してしまう

put した順や key のソート順が出てくると思い込むのは典型的なバグです。順序が必要なら LinkedHashMapTreeMap を選ぶ

Map を走査するで詳しく読む

Map.Entrynull チェックしてしまう

for-each が回している Map.Entry 自体は null にはなりません。null チェックすべきは中身の value (e.getValue() == null) であって、Entry そのものではありません

Map を走査するで詳しく読む

map.entrySet() の戻り型を勘違いする

entrySet()Set<Map.Entry<K,V>> を返します。List でも Map でもなく Set なので、インデックスでアクセスはできません。あくまで「ペアの集合」をループするためのものだと覚えましょう

Map を走査するで詳しく読む

Set で重複を排除

レッスンを読む

HashSet の順序を期待してしまう

HashSet は要素の並びを保証しません。for で回したときの順番がプログラムを再起動したら変わる、ということも普通に起きます。順序が大事なら LinkedHashSetTreeSet を使いましょう

Set で重複を排除で詳しく読む

equals / hashCode の実装を忘れる

自作クラスを HashSet に入れるときは、equalshashCode を両方ともオーバーライドする必要があります。片方だけだと「同じはずの 2 つが別物扱いされる」事故が起きます

Set で重複を排除で詳しく読む

null の扱いを誤解する

HashSetLinkedHashSetnull を 1 個だけ入れられます。TreeSetnull を入れようとすると NullPointerException で落ちます。Set ならどれでも同じ、と思い込まないようにしましょう

Set で重複を排除で詳しく読む

Collections クラスの便利メソッド

レッスンを読む

Arrays.asList で返るリストは固定長

Arrays.asList(1, 2, 3) の戻り値は addremove を呼ぶと UnsupportedOperationException が飛びます。サイズを変えたいなら new ArrayList<>(Arrays.asList(...)) のように ArrayList で ラップ してください

Collections クラスの便利メソッドで詳しく読む

Arrays.asList(int[]) は要素 1 個のリストになる

int 配列を Arrays.asList(arr) に渡すと、List<int[]> (要素数 1 の、配列を 1 個だけ持つリスト) になってしまいます。Collections.sort も期待どおりに動きません。プリミティブ配列を List 化したいときは for ループか Arrays.stream(arr).boxed() を使う必要があります

Collections クラスの便利メソッドで詳しく読む

Collections.sortComparator なしで呼ぶ条件

Collections.sort(list) が引数 1 つで動くのは、要素が Comparable を実装している型 (Integer String Double など) のときだけです。自作クラスのリストを sort したい場合は、クラスに implements Comparable<MyClass> を書くか、Collections.sort(list, comparator)Comparator を渡す必要があります

Collections クラスの便利メソッドで詳しく読む