對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 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方法的誤解以及無法補救的錯誤