Scanner#nextLine()留下一个剩余的换行符

时间:2016-03-19 23:53:09

标签: java java.util.scanner

我一直遇到Scanner#nextLine的问题。根据我的理解,nextLine()应返回当前输入流的其余部分,然后继续下一行。

<img
    src='img/1.jpg'
>

问题是最后一行。如果我将其保留为input.nextLine(),则循环的下一次迭代将接受该月的换行符。这是为什么?不应该在catch块中调用nextLine消耗该行的其余部分(包括换行符)并在下一次迭代中正确提示用户?注意:我决定打印它们以试图弄清楚发生了什么,但没有雪茄。

我从终端收集了一些输出来说明我的意思:

while (true){
      try{
        System.out.println("Please enter a month in numeric form");
        month = input.nextInt();
        System.out.println("Please enter a day in numeric form");
        day = input.nextInt();
        System.out.println("Please enter a two-digit year");
        if (input.hasNextInt() == true){
          year = input.next();
        }
        else{
          throw new java.util.InputMismatchException();
        }
        break;
      }
      catch(Exception e){
        System.err.println(e);
        System.out.println("\nOne of your inputs was not valid.");
        System.out.println(input.nextLine());
      }
    }

在有人将此标记为重复之前,请理解我已经查看了stackoverflow上next和nextLine之间的差异。 nextLine应该使用换行符,但这里似乎没有这样做。感谢。

3 个答案:

答案 0 :(得分:2)

if (input.hasNextInt() == true) { // prefer `if(input.hasNextInt())`
    year = input.next();
} else {
    throw new java.util.InputMismatchException();
}
输入badinput

会将input.hasNextInt()评估为false,这意味着else块将在没有消费 badinput的情况下执行(为此我们需要致电next() - 而不是nextLine(),因为您可能知道我们是否会在nextLine之后使用nextInt我们将使用剩余的行分隔符,而不是来自 next <的值/ em>喜欢,更多信息Scanner is skipping nextLine() after using next(), nextInt() or other nextFoo() methods)。

因为else块只是抛出异常,它将控制流移动到catch部分。这意味着我们正在跳过break所以我们的循环需要再次迭代。

现在在catch部分你只是打印

System.err.println(e);
System.out.println("\nOne of your inputs was not valid.");
System.out.println(input.nextLine());

打印异常e,字符串"\nOne of your inputs was not valid."nextLine()的结果(如前所述)将只消耗在最后nextInt()次调用后保留的行分隔符,因此我们仍然没有从扫描仪中消耗badinput

这意味着当循环开始另一次迭代并请求月份时,它会收到无​​效batinput的{​​{1}},因此int会抛出nextInt()。我们再次以InputMismatchException块结束,我们致电catch这次消耗nextLine()

现在,因为我们最终消耗了不正确的值循环将开始另一次迭代,我们将被要求月份的值。

要避免此类问题,请阅读以下示例:Validating input using java.util.Scanner。在第一个示例中,您将找到在提供时验证每个输入的方法

badinput

为避免多次编写此代码,请创建自己的实用程序方法。你甚至可以跳过你需要的条件,从数字到正面,如:

Scanner sc = new Scanner(System.in);
int number;
do {
    System.out.println("Please enter a positive number!");
    while (!sc.hasNextInt()) {
        System.out.println("That's not a number!");
        sc.next(); // this is important!
    }
    number = sc.nextInt();
} while (number <= 0);
System.out.println("Thank you! Got " + number);

使用此方法,您的代码可以缩减为

public static int getInt(Scanner sc, String askMsg) {
    System.out.println(askMsg);
    while (!sc.hasNextInt()) {
        System.out.println("That's not a number. Please try again");
        sc.next(); // consuming incorrect token
    }
    //here we know that next value is proper int so we can safely 
    //read and return it
    return sc.nextInt();
}

您可以添加该实用程序方法的另一个版本,您可以让程序员添加应该传递的数字条件。我们可以使用IntPredicate功能接口添加到Java 8中,这将允许我们使用lambdas创建条件,如

Scanner input = new Scanner(System.in);
int month = getInt(input, "Please enter a month in numeric form");
int day = getInt(input, "Please enter a day in numeric form");
int year = getInt(input, "Please enter a two-digit year");

用法:

public static int getInt(Scanner sc, String askMsg, IntPredicate predicate) {
    System.out.println(askMsg);
    int number;
    boolean isIncorrect = true;
    do {
        while (!sc.hasNextInt()) {
            String value = sc.next(); // consuming incorrect token
            System.out.println(value + " is not valid number. Please try again");
        }
        number = sc.nextInt();
        if (!predicate.test(number)) {
            System.out.println(number + " is not valid number. Please try again");
        }else{
            isIncorrect=false;
        }
    } while (isIncorrect);

    return number;
}

答案 1 :(得分:1)

我怀疑当你输入两位数的年份时,你正在使用next()读取它,所以它只会读取下一个字符串。即使您输入2位数年份的值,它也会留下2由您的nextLine()新行或空值读取,之后的任何内容将被遗留,包括输入无效值时的新行或回车。所以你的nextLine()里面的catch只读取了部分无效输入,但保留了新的行或回车。当您希望提示出现读取月份时,会导致异常发生。你可以在每个nextInt()或next()之后放置nextLine()来解决问题。

答案 2 :(得分:1)

请记住,Scanner没有看到您的打印语句,它只是将输入读作字符的。作为用户,您一次一行输入这些字符对于扫描仪而言毫无意义。

因此,您键入8<ENTER>(其中<ENTER>表示您的操作系统的实际换行符)。在nextInt()之后,8已被消耗。

然后输入2<ENTER>,进行待定输入<ENTER>2<ENTER>。请记住,到目前为止只消耗了8nextInt()跳过空白并返回2,从而消费 <ENTER>2

然后输入badinput<ENTER>,进行待定输入<ENTER>badinput<ENTER>。由于下一个标记不是有效的整数,因此抛出异常,并输入catch块,您可以在其中调用nextLine()。它消耗所有字符,包括第一个<ENTER>,并返回之前的文本,即空字符串。

此时,badinput<ENTER>在流中仍处于待处理状态,并在您回送时进行处理。

这是人们使用Scanner的方式的主要缺陷之一。 nextInt()不会消耗该行,只有令牌,剩下的就是排在后面。

使用Scanner

的情况变坏的示例
Please enter a month in numeric form
8 2 17
Please enter a day in numeric form
Please enter a two-digit year

因为用户在第一行输入了所有3个值,所以代码将获取值,但仍会打印下两个提示,即使这是不必要的。这很奇怪。

解决方案1:不要使用Scanner。它太奇怪了。太容易使用,太容易误用,也很难正确使用。

解决方案2:在每个nextLine()之后调用nextInt()以在接受的值之后刷新(静默消耗)任何额外的文本。如果您这样做,示例将如下所示:

Please enter a month in numeric form
8 2 17
Please enter a day in numeric form
2
Please enter a two-digit year
17

第一行的<SPACE>2<SPACE>17<ENTER>将被忽略。

如果您想要完整的错误处理,可以将逻辑扩展到if (! nextLine().trim().isEmpty()) {/*ERROR*/}