前提
最初の頃に躓いたので、記録も兼ねて記載します。
Scannerで入力を受け付けたあと、仕様外の入力(例:数字の入力を求めているのに文字を入力)などが起こった場合、エラー時の処理が無限に繰り返されてしまう対処法です。
なお、ターミナルやコマンドプロンプトが無限ループを起こしてしまったときは、
Windows:ctrl + C
Mac:Control + C
で終了できますので、「初めて知った」という方は是非ご参考ください^^
コンテンツ
下記のように年齢の入力をユーザーに求め、入力された数字に応じて
「成人です」
「未成年です」
と表示されるjavaのコードを書きます。
import java.util.Scanner;
public class ScannerBug {
public static void main(String[] args) {
// 入力受取のためのインスタンス生成
Scanner scanner = new Scanner(System.in);
while (true) {
int ageInput = 0;
System.out.println("年齢を入力してください");
try {
ageInput = scanner.nextInt();
if (ageInput >= 20 && ageInput < 130) {
System.out.println("成人です");
break;
} else if (ageInput < 20 && ageInput >= 0) {
System.out.println("未成年です");
break;
} else {
System.out.println("不正な値です");
}
} catch (Exception ex) {
// 数以外が入力されたときは再入力を求める
System.out.println("不正な値です");
}
}
scanner.close();
System.out.println("プログラムを終了します");
}
}
動作は以下のようになります。
「5」が入力されたときは「else if」に入り、「未成年です」と表示
「20」が入力されたときは「if」に入り、「成人です」と表示
「150」が入力されたときは「else」に入り、「不正な値です」と表示されたあと、「年齢を入力してください」と表示。負の値のときも同様。
つまり、想定内の数字「0~129」を入力されたときだけ、結果が表示されてプログラムが終了するようになっています。
しかし!
「a」などが入力されると画像のようにエラー時の処理がループしてしまいます。
原因
catchブロックに入ったあと、Scanner型の変数scannerに「a」が残っており、エラー時の処理がループしている。
詳細説明
まずはプログラムの流れを追います。
Scanner scanner = new Scanner(System.in);
Scanner型の変数scannerのインスタンスを生成。scannerにはユーザーの入力したものが入る。
ユーザーが「a」と入力。scannerには「a」が入る。
ageInput = scanner.nextInt();
int型の変数ageInputにint型の要素を入れようとするが、「a」は数字ではないため、エラーが発生。処理がcatchブロックへ進む。
System.out.println("不正な値です");
こちらは問題なく実行される。他の処理がないため、while(true)まで戻る。
このとき、scannerには「a」が入った状態のままとなっている!
ageInput = scanner.nextInt();
int型の変数に文字「a」を入れようとしているため、エラーが発生。catchブロックの処理が実行される。
以下ループ……
という流れになっていたんですね。
デバックモードで動かしてみると、変数にどのような値が入っているのか、コードのどの部分まで正常に動いているのか、確認できるので積極的に使っていきたいところです。
解決法
scannerをリセットすればいい、となります。方法はいくつかありますが、catchブロック内に
scanner.next();
を追記することで解消されます。
詳細説明
「.next();」は空白までの文字列の入力を受け取るメソッドです。似たものに「.nextLine();」がありますが、こちらは改行を判別する、という点で少し違います。
ユーザーが「a」を入力したとき、scannerには「a」が入ります。
その後、catchブロック内でscanner.next();を実行したとき、「a」を入力したときの入力内容が参照されており、「a」以外何も入力されていなければ、scannerにはnull(※ざっくり言うと”何もない”というもの)が入ります。
※catchブロック内にscanner.nextLine();と記載した場合は、ユーザーが「a」を入力した後に実行したエンターキーの入力が読み込まれ、改行コードの「\n」がscannerに入ります。
こうすることによって、変数のsannerが空になる、というわけです。
そのため、while(true)に処理が戻っても、scannerは空なので1からコードが実行されていきます。
最後に
Scannerを呼び出す度に変数名を変えてインスタンスを再生成したりしていたのですが、それはエレガントなコードとは言えないそうです。
そのため変数のscannerを使い回していたのですが、今度は上記の無限ループが発生してしまいました。
複数入力を求めるプログラムの場合、色々な箇所で「.next();」や「.nextLine();」を追記する必要があり、「見にくいのでは?」と疑問に思ったりもするのですが、現状の記録として、初心者の方のご参考になれば嬉しいです!
※勉強中の身ゆえ、記載内容に不適切な箇所などありましたら、諸先輩方からご指摘いただけますと幸いです。