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
)