1. 程式人生 > >對scanner.close方法的誤解以及無法補救的錯誤

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

文件句柄 什麽 也會 ioe exce ive find 進入 native

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 found

原因

下面是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:實際上有什麽原因關閉呢?)

讓我們把問題拓展開來

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

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

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

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

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

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