4
4

More than 3 years have passed since last update.

標準入力のScannerを1度closeしたら、2度と標準入力できない理由を調べた

Posted at

概要

Javaで標準入力ストリームのScannerを1度close()すると、再度Scannerクラスのインスタンスを生成しても標準入力ができなくなります。

Sample.java
Scanner sc1 = new Scanner(System.in);
sc1.close();
Scanner sc2 = new Scanner(System.in);
String input = sc2.nextLine(); // NoSuchElementException

理由は、「標準入力ストリームはプログラム開始時に開き、sc生成時に開くわけではない。一方で、sc.close()すると標準入力ストリームが閉じてしまうため」です。

「当たり前じゃん」と思ったらそれでOK。
以下、もう少し詳しく説明します。

私が誤解していた点

私は上述のエラーについて、「Scannerクラスのインスタンスを新しく生成しているんだから、読み込めてもいいのでは?」と思っていました。
これは、Scannerクラスとストリームの関係があいまいだったゆえの誤解です。

実際は new Scanner(System.in);
においてストリームはSystem.inです。
inはSystemクラスのstaticフィールドであり、InputStream型です。

System.java
/**
* The "standard" input stream. This stream is already
* open and ready to supply input data. Typically this stream
* corresponds to keyboard input or another input source specified by
* the host environment or user.
*/
public final static InputStream in = null;

sc.close()すると何が起きる?

scというスキャナをクローズするわけですが、その際に可能ならストリームも閉じます。
つまり、以下のコードでは、標準入力ストリームSystem.inが閉じます。

Sample.java
Scanner sc = new Scanner(System.in);
sc.close();

Scanner.close()を読むと、確かにsource(ここではSystem.in)に対してsource.close()を試みています。
なお、sourceはScannerインスタンス生成時の引数です。また、InputStreamはCloseableです。

Scanner.java
public void close() {
    if (closed)
        return;
    if (source instanceof Closeable) {
        try {
            ((Closeable)source).close();
        } catch (IOException ioe) {
           lastException = ioe;
        }
    }
    sourceClosed = true;
    source = null;
    closed = true;
    }

サンプルコードを用いた解説

Sample.java
//プログラム開始
//既に標準入力ストリームは開いている

//スキャナを生成したが、ここで標準入力ストリームが開くわけではない
Scanner sc1 = new Scanner(System.in);

//スキャナを閉じると同時に、System.in(標準入力ストリーム)も閉じる
sc1.close();

//スキャナを生成したが、ここで標準入力ストリームが開くわけではない
Scanner sc2 = new Scanner(System.in);

//標準入力ストリームが開いていないのでエラー
String input = sc2.nextLine(); // NoSuchElementException

//プログラム終了時に標準入力ストリームが開いていた場合、自動で閉じる
//プログラム終了

System.inを引数にスキャナを生成しても標準入力ストリームは開きませんが、sc.close()するとSystem.inまでclose()するということです。
これが、標準入力のスキャナを1度closeしたら、(再度スキャナを生成しても)2度と標準入力できない理由です。

eclipseが警告してくる理由(推測)

これ、実用上は「毎回Scannerクラスのインスタンスを生成する必要はない」「標準入力ストリームはプログラム終了時に自動的に閉じるから、明示的に閉じる必要はない」というだけの話です。

でも、eclipseで次のようなプログラムを書くと警告してくるんですよね。

Sample2.java
public static void main(String args[]) {
    inputName();
    inputName();
}
static void inputName() {
    Scanner sc = new Scanner(System.in); 
    //リソース·リーク:scが閉じられることはありません
    String name = sc.nextLine();
    System.out.println("名前:" + name);
}

「へぇ、じゃあ閉じます」と思ってinputNameの最後にsc.close()を追加すると、エラーが出るわけです。

なんなんだという感じですが、これはスキャナが標準入力ストリーム専用ではないことに起因すると推測しています。
つまり、scは明示的に閉じるべきストリームのスキャナかもしれないので、eclipseが保守的に警告を出してくれている、ということです。
標準入力ストリームの場合はこの警告は無視して構いません。

未解決点

①標準入力ストリームをclose()したあと、無理やり改めて開く方法は存在する?

そもそも「ストリームを閉じる」って具体的に何が起きている??
InputStream.close()のコードを呼んでも分からなかった(抽象クラスだし)

②標準入力ストリームは具体的にはどう開いている?

「プログラム開始時に自動で開く」らしいけど、特別な実行が行われている?それとも通常のクラスと同様の処理がされている?
後者なら、System.inはstaticフィールドだから、Systemクラスが最初に使われる時にメモリにロードされるはず。でもSystemクラスはインスタンス生成不能だし、プログラム開始時にSystem.inを呼び出しているということ?
①と合わせて、ストリーム周りやJVMの本質的な理解が必要そう。

4
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
4