Javaアプリケーション – タイマー③(scannerのclose、sleepメソッド、スローされたエラーのキャッチ、リファクタリング)

未分類

みなさんこんにちは、一星零哉です。

今回もコード解説の続きをやっていきますよー!

前回の記事

このアプリケーションのGitHubリポジトリ

今回の解説範囲

今回は以下の範囲を解説していきます。

} finally {
	scanner.close();
}

try {
	int setTime = Integer.parseInt(tmpSetTime);
	String srtMsg = "%s秒でタイマーを開始します。";
	System.out.println(String.format(srtMsg, setTime));
	
	for(int i = setTime; i > 0; i--) {
		System.out.println(String.format("%s秒", i));
		
		Thread.sleep(1000);
	}
	
	System.out.println("0秒");
	System.out.println("タイマーが終了しました。");
} catch(InterruptedException e) {
	System.out.println("スレッドスリープでエラーが発生しました。");
}

タイマーアプリのメイン処理の部分になりますね。

scannerのclose(補足)

以前のおみくじアプリで、リソース開放について以下のように説明しました。

  • 不要になったリソースは開放しましょう
  • Javaの内部リソースはガベージ・コレクションが自動的に開放してくれる
  • 外部リソースを扱うオブジェクト(Scanner、Connectionなど)はcloseメソッドで明示的に開放しなければならない

ただし、Scannerをcloseする際には注意点があります。

それは、「一度Scannerクラスをcloseすると、再度標準入力を使うことができなくなる」ことです。

以下のコードを例に見ていきます。

public static void main(String[] args) {
	// TODO 自動生成されたメソッド・スタブ
	Scanner scanner = new Scanner(System.in);
	System.out.println("文章を入力してください(1回目)。");
	System.out.println(scanner.nextLine());
	scanner.close();
	
	System.out.println("文章を入力してください(2回目)。");
	System.out.println(scanner.nextLine());
}

標準入力を読み取って標準出力するだけの簡単なプログラムですね。

実行結果は以下の通りです。

2回目の標準入力でscannerがcloseされていることにより、例外をスローします。

IllegalStateExceptionとは、「不正または不適切なときにメソッドが呼び出されたとき」にスローされる例外です。

一度closeされたscannerオブジェクトを使用しているのだから、当然の結果ですね。

では、以下のコードではどうでしょうか?

public static void main(String[] args) {
	// TODO 自動生成されたメソッド・スタブ
	Scanner scanner = new Scanner(System.in);
	System.out.println("文章を入力してください(1回目)。");
	System.out.println(scanner.nextLine());
	scanner.close();
	
	Scanner scanner2 = new Scanner(System.in);
	System.out.println("文章を入力してください(2回目)。");
	System.out.println(scanner2.nextLine());
	scanner.close();

}

scannerをcloseした後で新しくScannerオブジェクトをインスタンス化し、後者を使用して標準入力の読み取りを試してみます。

結果は以下の通りです。

こちらも例外をスローしてしまいました。

NoSuchElementExceptionは「リクエストされている要素が存在しない」ときに発生する例外です。

このようになってしまうのは、Scannerオブジェクトのcloseによって標準入力ストリームも一緒にクローズしてしまうためです。

scannerオブジェクトをクローズするのは、もう標準入力を使わないタイミングが来てからにしましょう。

sleepメソッド

sleepメソッドは、Threadクラスの静的メソッドで、引数で渡した時間分処理を一時的に止めるメソッドです。

引数の単位はミリ秒(1/1000秒)であることに注意が必要です。

for(int i = setTime; i > 0; i--) {
	System.out.println(String.format("%s秒", i));
	
	Thread.sleep(1000);
}

このコードではカウンタ変数を「〇秒」の書式にフォーマットしてから標準出力をします。

そのあと1秒処理を止めたのち、ループの最初に戻ります。

スローされたエラーのキャッチ

sleepメソッドは例外発生時にInterruptedExceptionという例外をスローします。

Javaの公式ドキュメントを参照してみると、sleepメソッドは以下のように定義されています。

public static void sleep(long millis)
                  throws InterruptedException

メソッドの宣言でthrowsには「このメソッドで例外が発生したら、InterruptedExceptionを投げるから、呼び出し元で対応してね」という意味が込められています。

この場合、呼び出し元ではさらに上位階層の呼び出し元に例外をthrowsするか、呼び出し元で例外に対処するかのどちらかを求められます。

対応しなければコンパイルエラーとなります。

今回はtry-catchで処理のブロックを囲って、catchブロックで例外を受け止めることにします(というより、これ以上の呼び出し元は存在しないため、ここで対処するしかありません)。

今回は例外発生時に「スレッドスリープでエラーが発生しました。」と表示するようにしました。

catch(InterruptedException e) {
	System.out.println("スレッドスリープでエラーが発生しました。");
}

throwsされた例外をcatchするには、catchしたい例外の型を()の中で宣言します。

リファクタリング

さてさて、今回もリファクタリングを行います。

対象は主に以下ですね。

  1. 変数を宣言するのをクラスの頭に持ってくる
  2. Scannerのインスタンスをtry-with-resources化する

前回と対象が変わらないような気がしますね。。。

アプリケーションを設計しながらコード書いていると、ついつい可読性は二の次になってしまいますね。

import java.util.Scanner;

public class exeTimer {

	public static void main(String[] args) {
		// TODO 自動生成されたメソッド・スタブ
		String tmpSetTime;
		int setTime;
		String srtMsg = "%s秒でタイマーを開始します。";
		
		// 入力された値が数字以外の場合、処理を終了する
		try(Scanner scanner = new Scanner(System.in)) {
			System.out.println("時間を秒単位で設定してください。");
			tmpSetTime = scanner.nextLine();
			
			for(int i = 0; i < tmpSetTime.length(); i++) {
				if(!Character.isDigit(tmpSetTime.charAt(i))) {
					System.out.println("数字を入力してください。");
					return;
				}
			}
		}
		
		// 入力された数字のカウントダウンを実施する
		try {
			setTime = Integer.parseInt(tmpSetTime);
			System.out.println(String.format(srtMsg, setTime));
			
			for(int i = setTime; i > 0; i--) {
				System.out.println(String.format("%s秒", i));
				
				Thread.sleep(1000);
			}
			
			System.out.println("0秒");
			System.out.println("タイマーが終了しました。");
		} catch(InterruptedException e) {
			System.out.println("スレッドスリープでエラーが発生しました。");
		}
		

	}

}

まとめ

今回のまとめです!

  • Scannerクラスをcloseすると標準入力自体がcloseされる
    • closeはもう標準入力を使用しないタイミングになってから
  • sleepメソッドで処理を一時的に止めることができる
    • 処理を止める時間は引数で指定するが、この単位はミリ秒であることに注意
  • 非チェック例外はtry-catchで受け止めて処理しよう

これでタイマーアプリの解説は終わりです!お疲れ様でした!

この記事を読み終えたら、各種SNSのフォローもよろしくお願いします!

X

Instagram

参考

IllegalStateException NoSuchElementException sleepメソッド

コメント