Java Scanner的hasNext()方法
在編程筆試(或者某些場景)中,可能存在這樣的需求:程序被要求接收不確定數量的一些字符串或者是數字,然后對接收的數據進行相關的處理。
假設這樣一個場景,程序被要求接收不定數量的一些整數,然后計算這些數字的和。來看下面這個程序:
import java.util.Scanner;
public class Test {
public static void main(String[] args) {
Scanner s = new Scanner(System.in);
int sum = 0;
while(s.hasNextInt()) {
sum += s.nextInt();
}
System.out.println(sum);
}
}
我們的預期是:程序開始運行后,我們通過鍵盤在終端鍵入一行不定數量的整數,按下回車,程序緊接着打印出整數之和,然后結束運行。看上去似乎條例清晰,邏輯嚴密。不過程序實際運行的情況與我們的預期出現了一點偏差:在我們輸入數據並按下回車之后,光標移動到了下一行,不過程序並沒有打印出我們期待的結果,也沒有結束運行,它“停下”了,或者說,它似乎在等待用戶繼續輸入。如果此時你繼續輸入一行整數,然后按下回車,光標移動到下一行,情況不會發生變化。甚至你不輸入任何內容,直接按下回車,情況仍然不會發生變化。但是如果你輸入了一個浮點數或者是一個字符串,程序會緊接着打印出你想要的結果,然后結束運行。這很好理解,因為你輸入的不是整數,循環會因此結束。
再來看這樣一個場景,程序被要求接收不確定數量的一些字符串,然后將其逆序輸出。來看下面這個程序:
import java.util.Scanner;
public class Test {
public static void main(String[] args) {
Scanner s = new Scanner(System.in);
String str = "";
while(s.hasNext()) {
str = s.next() + " " + str;
}
System.out.println(str);
}
}
我們的預期是:程序開始運行后,我們通過鍵盤在終端鍵入一行不定數量的字符串,按下回車,程序緊接着逆序打印出字符串,然后結束運行。然而這個程序實際運行的情況更加糟糕,似乎不論我們輸入任何類型、任何數量的內容,程序都不會從循環中跳出,就好像沒有什么條件能夠使s.hasNext()的結果為false。
這種情況令人感到沮喪,看來我們必須調查一下到底哪里出現了問題。顯而易見,問題的出現肯定和Scanner類的hasNext()方法有關,那就讓我們首先來看一下 jdk 的開發者對這個方法的描述:
Returns true if this scanner has another token in its input.
This method may block while waiting for input to scan.
The scanner does not advance past any input.
重點看這一句:**This method may block while waiting for input to scan. **大意是此方法可能會在等待輸入時阻塞。這樣一解釋就好辦了,我們在循環體里面打印一下 hasNext() 方法的返回值看看:
public static void main(String[] args) {
Scanner s = new Scanner(System.in);
boolean b;
while(b = s.hasNext()) {
System.out.println(b);
System.out.println("your input is " + s.next());
}
System.out.println(b);
}
程序執行的結果如下:
i am iron man // 這句是輸入,下面的都是輸出
true
your input is i
true
your input is am
true
your input is iron
true
your input is man
// 輸出換行到這兒以后程序並未從循環中跳出
可以看到,程序將輸入的最后一個單詞man打印出以后,既沒有跳出循環,打印出false;也沒有進入下一次循環,打印出true。s.hasNext()在這兒發生了阻塞,程序在等待用戶輸入,而用戶不想再繼續輸入了。一方面,程序並不把空格、制表符以及回車換行這樣的不可見字符當作輸入,空白字符被當作輸入內容的分隔符,回車換行則被用於提交輸入;另一方面,似乎用戶輸入任何可見字符,s.hasNext()都會返回true。
真的,沒有辦法了嗎?
其實是有解決辦法的,而且不止一種,請看下面:
方案一(Windows系統適用):ctrl + Z
如果是在Windows系統的cmd窗口中運行此程序,按下ctrl+Z,窗口會出現^Z這樣的符號,按下回車,程序打印出false,然后結束運行。請注意,程序是正常結束的,所以說:並非用戶輸入任何可見字符,s.hasNext()都會返回true,將^Z作為輸入,hasNext()會返回false。這個方案在cmd窗口中有效,而在IDEA自帶的控制台中無效,不過在IDEA中按下ctrl+D可以達到同樣的效果。
另外,有StackOverflow用戶指出,對於Linux/Unix/MacOS環境,ctrl+D可以達到同樣的效果,不過我沒有做過相關的測試,僅僅在這里提一下。
方案二:使用hasNext(String pattern)
可以使用hasNext()的重載方法hasNext(String pattern)通過特殊終止符結束循環,就像下面這樣:
public static void main(String[] args) {
Scanner s = new Scanner(System.in);
while(!s.hasNext("exit")) {
System.out.println("your input is " + s.next());
}
}
當輸入exit,循環結束。有點類似於在mysql命令行程序中輸入quit,或者是在cmd中輸入exit。
方案三:使用break跳出循環
在循環體中通過特殊終止符觸發break,例如下面這樣:
public static void main(String[] args) {
Scanner s = new Scanner(System.in);
while(s.hasNext()) {
String tmp = s.next();
if(tmp.equals("quit")) break;
System.out.println("your input is " + tmp);
}
}
當輸入quit,循環終止。
方案四:重定向輸入流
前面說過,hasNext()方法可能會在等待輸入時阻塞,可能的意思就是說:存在某種情況,hasNext()不會發生阻塞。比如說下面介紹的這種方案:
既然問題是由鍵盤輸入帶來的,不如就把產生問題的源頭干掉,我們可以重定向標准輸入使得系統從文件而不是從鍵盤讀取輸入。操作很簡單,提前在一個文本文件中編輯好輸入內容,然后在cmd中執行程序,只需給執行命令增加一點內容,像下面這樣:
java Test < text.txt //text.txt中包含了待輸入的內容。
按下回車,程序從text.txt中讀取內容,告別從鍵盤獲取輸入。
這個方案有一個優點是用戶不再需要手動從鍵盤輸入數據,尤其是當我們做測試的時候,這種方式能幫我們節省很多時間和精力,輸入量越大,這種優勢越為明顯。IDEA中可以配置輸入重定向,打開 Run Configurations 就可以找到,這里我不詳細描述了。
另外,一些朋友提到:在編程網站中提交代碼while(s.hasNext())可以自己跳出循環,而在本地 IDEA 中使用while(s.hasNext())沒有辦法退出循環,其實就是因為在線編程網站將輸入流重定向了。
總結四種方案:
| 方案號 | 辦法 | 備注 |
|---|---|---|
| 方案一 | ctrl+Z | Windows系統適用 |
| 方案二 | 使用hasNext(String pattern) | |
| 方案三 | 使用break跳出循環 | 最朴素的思路 |
| 方案四 | 重定向輸入流 | 節省時間和精力 |
參考資料:
[1] Robert Sedgewick and Kevin Wayne 著,謝路雲 譯.算法.北京:人民郵電出版社,2012:24.
[2] trutheality.StackOverflow.https://stackoverflow.com/questions/10490344/how-to-get-out-of-while-loop-in-java-with-scanner-method-hasnext-as-condition
[3] while(hasNextInt())為什么不會結束循環.百度知道.https://zhidao.baidu.com/question/135843130068219805.html
[4] java的Scanner類的hasNext()方法問題.百度知道.https://zhidao.baidu.com/question/505075549.html
[5] 如何在JAVA中使用SCANNER方法“HASNEXT”作為條件退出WHILE循環?.dovov編程網.http://www.dovov.com/javascannerhasnextwhile.html
