前言:從輸入流到現代化 API 的演進
控制台輸入是 Java 初學者接觸的第一個 I/O 場景。隨著 JDK 版本演進,API 經歷了三代:**字元流橋接 → 正則掃描器 → 專用控制台物件**。本文系統梳理三代用法、優缺點與適用場景。
第一代:JDK 1.0~1.4 —— BufferedReader 橋接輸入流
import java.io.*;
public class ConsoleInputV1 {
public static void main(String[] args) throws IOException {
// System.in (InputStream) → InputStreamReader (橋接) → BufferedReader (緩衝)
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
System.out.print("請輸入姓名: ");
String name = br.readLine(); // 僅支援讀取整行字串
System.out.print("請輸入年齡: ");
String ageStr = br.readLine();
int age = Integer.parseInt(ageStr); // 手動轉型,拋 NumberFormatException
System.out.printf("姓名: %s, 年齡: %d%n", name, age);
}
}
特點
- **唯一選擇**(JDK 1.4 前)。
- **僅回傳 String**,基本型別需手動
parseXxx。
- 需處理
IOException(受檢異常)。
- 無提示功能,
readLine() 不支援密碼隱藏。
第二代:JDK 5.0 —— Scanner 正則掃描器
import java.util.Scanner;
public class ConsoleInputV2 {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.print("請輸入姓名: ");
String name = sc.nextLine(); // 讀整行
System.out.print("請輸入年齡: ");
int age = sc.nextInt(); // 直接讀 int,自動轉型
System.out.print("請輸入身高: ");
double height = sc.nextDouble(); // 直接讀 double
System.out.print("請輸入是否學生: ");
boolean isStudent = sc.nextBoolean(); // true/false
System.out.printf("姓名: %s, 年齡: %d, 身高: %.1f, 學生: %b%n",
name, age, height, isStudent);
sc.close(); // 關閉掃描器(也會關閉 System.in,後續不可再用)
}
}
核心優勢
| 方法 | 功能 |
next() | 下一個 token(空白分隔) |
nextLine() | 整行(含空白) |
nextInt/Long/Short/Byte | 直接讀基本型別整數 |
nextDouble/Float | 直接讀浮點數 |
nextBoolean() | 讀 true/false |
nextBigInteger/BigDecimal | 大數字型別 |
hasNextXxx() | 預檢下一 token 型別 |
useDelimiter(pattern) | 自定義分隔符(正則) |
注意事項
nextInt() 後緩衝區殘留換行符,後續 nextLine() 會讀到空行 → **混用時在 nextInt() 後呼叫一次 nextLine() 清空換行**。
sc.close() 會關閉 System.in,**全程只能建立一個 Scanner**,否則後續讀取失效。
- 非執行緒安全,多執行緒需同步或各自建立。
第三代:JDK 6.0 —— Console 專用控制台物件
import java.io.Console;
public class ConsoleInputV3 {
public static void main(String[] args) {
Console console = System.console();
if (console == null) {
System.err.println("無法取得 Console 物件(可能在 IDE 運行、重導向、非終端機環境)");
return;
}
// 1. 讀取字串(可帶提示)
String name = console.readLine("請輸入姓名: ");
// 2. 讀取密碼(不回顯,回傳 char[] 避免字串常駐記憶體)
char[] pwd = console.readPassword("請輸入密碼: ");
String password = new String(pwd); // 用完立即清除
java.util.Arrays.fill(pwd, ' '); // 安全清除
// 3. 格式化輸出
console.printf("歡迎 %s,密碼長度 %d%n", name, password.length());
// 4. 取得 Reader/Writer 進行進階操作
// console.reader() / console.writer()
}
}
獨特優勢
- 密碼隱藏輸入:
readPassword() 回傳 char[],非 String,可主動清除,減少記憶體殘留風險。
- 內建提示:
readLine("prompt: ") 一體化。
- 編碼自動適配:根據終端機編碼處理中文等非 ASCII 字元。
- 格式化輸出:
printf() 直接支援。
重大限制
- IDE 運行時
System.console() 回傳 null(Eclipse、IntelliJ、VS Code 內建終端多為管道而非真實 TTY)。
- **僅適用真實終端機**(cmd、bash、PowerShell、Terminal.app)。
- 無直接讀基本型別方法,仍需自行解析。
三代對比總結表
| 維度 | BufferedReader | Scanner | Console |
| JDK 版本 | 1.0+ | 5.0+ | 6.0+ |
| 核心能力 | 唯讀行字串 | 正則掃描 + 直接讀基本型別 | 真實終端機專用、密碼隱藏 |
| 適用環境 | 任何有 System.in 的環境 | 任何有 System.in 的環境 | **僅真實 TTY/終端機** |
| IDE 相容性 | ✅ 完美 | ✅ 完美 | ❌ 回傳 null |
| 密碼隱藏 | ❌ | ❌ | ✅ readPassword() |
| 基本型別直接讀 | ❌ 手動 parse | ✅ | ❌ |
| 編碼處理 | 手動指定 | 平台預設 | 自動適配終端 |
| 執行緒安全 | ❌ | ❌ | ❌ |
最佳實踐建議
- 教學/測試/IDE 環境:**首選 Scanner**,API 豐富、支援基本型別、IDE 完美相容。
- 需密碼隱藏的生產 CLI 工具:**首選 Console**,但需檢測
null 並降級到 Scanner(提示明文輸入)。
- 高效能/大量資料輸入:BufferedReader + 手動解析,最低開銷。
- 混用 nextXxx() 與 nextLine():**次呼叫
nextLine() 清空換行符**。
- Scanner 關閉策略:全程單例,
try-with-resources 管理,或不關閉(JVM 結束自動回收)。
- 安全清除密碼:
Arrays.fill(charArray, ' '),勿依賴 GC。
綜合範例:自動適配 Console/Scanner
import java.io.Console;
import java.util.Scanner;
public class SmartConsoleInput {
public static void main(String[] args) {
Console console = System.console();
Scanner scanner = new Scanner(System.in);
// 讀取姓名
String name = (console != null)
? console.readLine("姓名: ")
: readLine(scanner, "姓名: ");
// 讀取密碼
String password;
if (console != null) {
char[] pwd = console.readPassword("密碼: ");
password = new String(pwd);
java.util.Arrays.fill(pwd, ' ');
} else {
System.out.print("密碼(明文): ");
password = scanner.nextLine();
}
System.out.printf("登入: %s / %s%n", name, "*".repeat(password.length()));
scanner.close();
}
static String readLine(Scanner sc, String prompt) {
System.out.print(prompt);
return sc.nextLine();
}
}
總結
- 三代 API 各有定位:**BufferedReader 基石、Scanner 通用主力、Console 安全專用**。
- **IDE 開發階段 Scanner 無敵**;**部署到真實終端的 CLI 工具 Console 獨步**。
- 理解
System.console() == null 的成因與對策,是 CLI 開發者的必修課。
- 安全場景**務必用 char[] 接收密碼,用畢立即清除**。