對scanner.close方法的誤解以及沒法補救的錯誤

scanner錯誤關閉致使的異常

public class test2 {

  public static void main(String[] args) {
    Scanner scanner1 = new Scanner(System.in);
    System.out.println("run scanner1.close()");
    scanner1.close();

    Scanner scanner2 = new Scanner(System.in);
    System.out.println("run scanner2.nextLine()");
    scanner2.nextLine();

  }
}

可是會在scanner2.nextLine()調用時拋出異常
java.util.NoSuchElementException: No line foundjava

緣由

下面是scanner的源碼程序員

// The input source
    private Readable source;
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;
    }

因此調用close方法並不單單關閉scanner類,同時關閉了初始化時做爲參數傳入的Readable對象。
在示例代碼中scanner1.close();關閉了System.in,因此雖然初始化scanner2沒有問題,可是readLine()會報錯。安全

是否能夠經過從新開啓System.in的方式補救呢?

遺憾的是,至少做爲java的使用者來講是不能夠的,除非咱們能控制jvm運行。
這涉及到System.in是如何開啓的,簡單來講由於System.in是特殊的系統資源,由jvm負責開啓,沒法經過java代碼從新初始化System.in。
若是查看System.in的代碼咱們就能發現它經過native方法實現初始化,在native方法中將控制檯或文件句柄傳輸給System.in來完成。
一樣的,Systerm.out System.err也是沒法從新被開啓的資源,對於它們,close方法應該被謹慎的調用。
(ps:實際上有什麼緣由關閉呢?)eclipse

讓咱們把問題拓展開來

實際上,全部的可以以System.in/out/err爲構造器參數的包裝器類的close方法都應該被考慮,
能夠看到jdk的設計者並無區別對待普通的流和System.in/out/err,
而包裝器類的close方法關閉底層流對於普通流來講是很合理的,所以咱們能夠推測事實上其餘的包裝器的close方法也可能致使關閉System.in/out/err。
經常使用的bufferinputstream就是如此。jvm

jdk7引入的帶資源的try語法糖產生隱蔽的問題

jdk7引入的帶資源的try語法糖隱式的幫助程序員調用close方法,
遺憾的是該方法也會產生上述問題,甚至更難被發現。ide

咱們有必要積極的關閉再也不須要的流嘛?如何對待io.close?如何預防該問題?

從性能的角度來講,積極關閉流是必須的,實際上若是咱們使用findbugs等代碼規範工具,能發現關閉io是被強烈推薦的。
若是你使用idea的話,建議在close以前,使用快捷鍵ctrl+b進入close方法的源代碼來查看其關閉機制。這種方法很是簡便,固然eclipse應該也有插件能夠實現相似功能。
咱們也能夠經過包裝System.in/out/err來安全的使用它們,實際上利用裝飾器模式,覆蓋System.in/out/err的close方法便可。工具

相關文章
相關標籤/搜索