Tại sao phải khai báo private static Scanner trước hàm main

    package hocjava;

import java.util.Scanner;

public class xinchaojava {



	public static void main(String[] args) {
		
		Scanner hello = new Scanner(System.in);
		System.out.println(" nhap chuoi :");
		String sc = hello.nextLine();
		System.out.println(" chuoi la " +sc);
		
	}
}

khi viết đoạn này eclipse gợi ý phải thêm :
private static Scanner hello;
vào trước hàm main , vậy cho em hỏi là tại sao phải thêm nó vào ạ ?
em mới học câu hỏi có gì chưa đúng hay khó hiểu , mong người góp ý cho em với ạ

private: thực sự không cần vì đây là biến riêng :smiley: của hàm main() thôi.
static: khởi tạo một lần duy nhất cho mỗi hàm, sẽ nhanh hơn (đỡ GC) và ít tốn mem.

5 Likes

em chưa hiểu lắm " đỡ GC " là gì vậy ạ !!
em coi trên mạng là gas Chromatography phương pháp sắc khí gì đó :thinking:

Garbage Collector :thinking:

2 Likes

Một application thì ko cần thiết phải khởi tạo nhiều Scanner, có thể chỉ cần 1 Scanner duy nhất. Việc gợi ý trên Eclipse thực ra cũng muốn bạn chỉ dùng 1 Scanner vì nhiều Scanner chỉ thừa thãi mà thôi.

Có nhiều cách để bạn có thể chỉ cần dùng 1 Scanner:

  1. Khai báo Scanner với scope là global như Eclipse gợi ý.
  2. Khai báo Scanner ở hàm main thôi và dùng Scanner này cho tất cả các function khác bằng cách truyển tham số:
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);

        int n    = readInt(sc);
        String s = readString(sc);
        double d = readDouble(sc);
    }

    private static int readInt(Scanner sc) {...}
    private static String readString(Scanner sc) {...}
    private static double readDouble(Scanner sc) {...}
3 Likes

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, outerr 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, outerr đế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, InputStreamPrintStream đề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 :cry:)

10 Likes

Nghe có vẻ nguyên nhân sâu xa là do thiết kế sai lầm :joy:

3 Likes
83% thành viên diễn đàn không hỏi bài tập, còn bạn thì sao?