Câu này trước có người hỏi rồi, và cũng là 1 trong các thắc mắc và lỗi với mấy bạn newbie học Java. Nếu giải thích ngắn gọn thì chỉ cần lấy 1 câu từ JavaDocs.
Giải thích chi tiết như thế này:
Khi Java bắt đầu chạy hàm main(), JavaRuntime đã mở sẵn 3 IO stream:
-
System.in: Standard input stream, có data type là InputStream, kết nối và nhận dữ liệu từ bàn phím.
-
System.out: Standard output stream, có data type là PrintStream, kết nối và xuất dữ liệu ra màn hình.
-
System.err: Standard error stream, có data type là PrintStream, cũng kết nối và xuất dữ liệu ra màn hình. (giống System.out)
Phần khai báo 3 field in, out và err trong file System.java nằm trong JDK. Cả 3 field này đều khai báo dạng public final static.
public final class System {
public final static InputStream in = nullInputStream();
public final static PrintStream out = nullPrintStream();
public final static PrintStream err = nullPrintStream();
private static native void registerNatives();
static {
registerNatives();
}
}
Vì các fields là static, ClassLoader khởi tạo các static fields này trước khi chạy hàm main(), và gọi static block, trong static block chạy tiếp registerNatives(). registerNatives() được khai báo là native, là nơi gán in đến bàn phím, out và err đến màn hình.
Cách sử dụng khác của static field là hiện thực Singleton Pattern. Nếu trong lúc code vô tình gán 1 trong các static field này bằng 1 object khác hay null, như in chẳng hạn, thì biến System.in mất đi kết nối tới màn hình.
System.in = null;
Ngoài ra, InputStream và PrintStream đều implement interface Closeable. Nếu gọi method close() thì cũng mất kết nối luôn, như gọi output.close() là mất kết nối màn hình, viết output.println("...") cũng không hiển thị gì cả.
System.out.close();
Xét đoạn code sau, do Scanner hiện thực interface là Closeable nên có thể gọi close() trên Scanner.
Scanner s1 = new Scanner(System.in);
s1.close();
Scanner s2 = new Scanner(System.in);
s2.nextLine();
Theo chú ý của JavaDocs
Nếu thực thi s1.close() thì ngoài close s1, thì s1 close luôn input source System.in của nó. Nói cách khác. s1.close() gọi tới System.in.close().
Như mình nói ở trên, nếu gọi System.in.close() thì System.in mất kết nối đến bàn phím và System.in không nhận được bất kì dữ liệu từ bàn phím nữa.
Nếu tiếp tục thực thi 2 lệnh còn lại của s2. Lệnh khởi tạo s2 thì không có lỗi, vì chỉ có bind System.in làm input source cho s2. Tuy nhiên, nếu thực thi s2.nextLine() báo lỗi và văng exception, nextLine() gọi System.in và yêu cầu đưa dữ liệu. Vì System.in đã close nên throw IOException.
java.io.IOException: Stream closed
Scanner ngoài hiện thực interface Closeable, nó cũng hiện thực interface AutoCloseable. Vì vậy, JavaRuntime tự động gọi method close() khi Scanner object không có biến nào trỏ tới, Scanner không cần thiết phải có trong chương trình. Trường hợp này xảy ra khi Scanner được tạo trong 1 method, ví dụ:
public class Main {
public static void main(String args[]) {
doIOStream();
// ...
// ...
getIOException();
}
public static void doIOStream() {
Scanner sc = new Scanner(System.in);
//...
}
public static void getIOException() {
Scanner esc = new Scanner(System.in);
esc.nextInt();
}
}
Scope của biến sc lúc này chỉ nằm trong method doIOStream(). Vì vậy, sau khi thực hiện doIOStream() trở về main(), Scanner do sc trỏ tới không cần thiết. JavaRuntime gọi tới sc.close(), kéo theo System.in.close(), System.in mất kết nối đến bàn phím.
Sau đó, main() gọi tới getIOException(), tạo esc với input source là System.in, nhưng lúc gọi esc.nextInt() bị văng IOException, do System.in đã close trước đó.
Để tránh tình trạng như trên, Eclipse mới khuyến khích báo Scanner là static field luôn. Do tính chất của static nên Scanner không bao giờ bị gọi close().
(Lần nào mình viết cũng dài, không có khả năng viết ngắn gọn súc tích
)