`Scanner.nextLine()`忽略空白字符吗?(Java)

huangapple go评论78阅读模式
英文:

Scanner.nextLine() ignores whitespace? (Java)

问题

以下是翻译好的内容:

@Test
public void testBattle() throws IOException{
  String input = "前进\n左转\n拿尖刺的棍子\n后退\n向右走\n普通攻击\n普通攻击\n退出\n";
  provideInput(input);
  actual = new File("src/main/testFiles/testBattle.txt");
  expected = new File("src/main/testFiles/testBattleExpected.txt");
  PrintStream o = new PrintStream(actual);
  System.setOut(o);

  ui.gameLoop();
  assertTrue(FileUtils.contentEqualsIgnoreEOL(actual, expected, null));
}

这是提供输入的方法:

private void provideInput(String data) {
  String newdata = data.trim();
  testIn = new ByteArrayInputStream(newdata.getBytes());
  System.setIn(testIn);
}

我正在使用 scanner nextLine 进行处理:

command = input.nextLine().toLowerCase().trim();

其中这里的 "input" 表示扫描器对象。但我仍然在获取以下错误,特别是当第一个 "普通攻击" 被传递到 System.in 时:

java.util.NoSuchElementException: 找不到行

出现在上面的那一行。我以为 nextLine 会忽略空格?如果不是,是不是我格式化字符串时弄错了?

编辑:
从 UI.gameLoop() 的前几行,我只初始化扫描器一次。

public void gameLoop() throws IOException, JsonSyntaxException {
  input = new Scanner(System.in);
  engine = new GameEngine(path);
英文:

I have a command-line game and am testing with JUnit, this is the test:

@Test
  public void testBattle() throws IOException{
    String input = "go forward\ngo left\ntake Pointy Stick\ngo backward\ngo " +
            "right\nnormal attack\nnormal attack\nquit\n";
    provideInput(input);
    actual = new File("src/main/testFiles/testBattle.txt");
    expected = new File("src/main/testFiles/testBattleExpected.txt");
    PrintStream o = new PrintStream(actual);
    System.setOut(o);

    ui.gameLoop();
    assertTrue(FileUtils.contentEqualsIgnoreEOL(actual, expected, null));

  }

And this is the provide input method:

 private void provideInput(String data) {
    String newdata = data.trim();
    testIn = new ByteArrayInputStream(newdata.getBytes());
    System.setIn(testIn);
  }

I'm doing scanner nextline so:

command = input.nextLine().toLowerCase().trim();

where "input" here represents the scanner object
but I'm still getting this error, specifically when the first "normal attack" is passed into System.in

java.util.NoSuchElementException: No line found

on that line above. I thought nextline ignored whitespace? If not did I format my string wrong to not include it?

EDIT:
From the first few lines of UI.gameLoop() I only initialize the scanner once.

public void gameLoop() throws IOException, JsonSyntaxException {
    input = new Scanner(System.in);
    engine = new GameEngine(path);

答案1

得分: 2

我以为 nextLine 会忽略空白字符?

不是的。根据javadocs,它会读取直到下一个行尾序列(或EOF),然后返回从行尾序列之前但不包括行尾序列的所有内容。

如果你遇到了

java.util.NoSuchElementException: 找不到行

这意味着Scanner已经到达了输入流的末尾,或者(可能是)Scanner正尝试从在代码的其他位置被提前关闭的输入流中读取。

我们可以对实际问题进行猜测,但是如果没有看到您的最小可复现示例,我们无法进一步解决。


实际上,我刚刚发现了一个线索:

> ... 我正在使用 JUnit 进行测试 ...

这可能是问题的根源。JVM在其生命周期中只能“读取到System.in的末尾”一次。如果您有两个或更多需要执行此操作的JUnit测试,除非您可以找到一种方法来“模拟”System.in变量,否则这将是困难的。

重新组织代码,以便从作为参数传递给游戏代码的某个流中获取输入,可能会更简单。通过重新组织,您可以更容易地编写单元测试。

英文:

> I thought nextline ignored whitespace?

Nope. According to the javadocs, it reads past the next end-of-line sequence (or to EOF), and then returns everything up to but not including the end-of-line sequence.

If you are getting

java.util.NoSuchElementException: No line found

that means that the Scanner has already reached the end of the input stream, or (maybe) the Scanner is trying to read from an input stream that was prematurely closed somewhere else in your code.

We can make guesses about what the real problem is, but without seeing >>your<< minimal reproducible example, we can't take this much further.


Actually, I just spotted a clue:

> ... I am testing with JUnit ...

This is possibly at the root of your problems. A JVM can only "read to the end of System.in" once in its lifetime. If you have two or more JUnit tests that need to do this, it is going to be difficult, unless you can find a way to "mock" the System.in variable.

It may be simpler to reorganize your code so that you take the input from some stream that is passed to your game code as a parameter. By reorganizing you can make it easier to write unit tests.

答案2

得分: 0

以下是翻译好的内容:

这里可供参考的信息有限,但我猜测您正在创建多个 Scanner,每次想要读取一行时都创建一个 Scanner。这通常在与人类进行交互时效果还不错,因为人类输入速度较慢,但当每个 Scanner 的预读取导致消耗了多行时,这种方法就会失败。

您可以在以下最小化可复现示例中看到差异:

import java.util.*;
import java.io.*;

class Foo {
  public static void main(String[] args) throws Exception {
    String newdata = "go forward\ngo left\ntake Pointy Stick\ngo backward\ngo " +
                      "right\nnormal attack\nnormal attack\nquit\n".trim();
    ByteArrayInputStream testIn = new ByteArrayInputStream(newdata.getBytes());
    System.setIn(testIn);

    boolean includeBug = Boolean.valueOf(args[0]);

    if (includeBug) {

      for(int i=0; i<8; i++) {
        Scanner input = new Scanner(System.in);
        System.out.println("Read: " + input.nextLine());
      }

    } else {

      Scanner input = new Scanner(System.in);
      for(int i=0; i<8; i++) {
        System.out.println("Read: " + input.nextLine());
      }

    }
  }
}

includeBug 为 true 时,它为每一行创建一个新的 Scanner,并且会像您所说的那样崩溃。如果为 false,则创建一个单独的 Scanner 并且可以正确工作:

$ javac Foo.java
$ java Foo true
Read: go forward
Exception in thread "main" java.util.NoSuchElementException: No line found
        at java.util.Scanner.nextLine(Scanner.java:1540)
        at Foo.main(Foo.java:17)

$ java Foo false
Read: go forward
Read: go left
Read: take Pointy Stick
Read: go backward
(等等)

请注意,我只翻译了您提供的代码部分,不包括其他内容。

英文:

There's not a lot to go on, but I'm guessing you're creating multiple Scanners, one for each time you want to read a line. This usually works ok interactively since humans are slow typers, but fails when each Scanner's readahead ends up consuming multiple lines.

You can see the difference in this MCVE:

import java.util.*;
import java.io.*;

class Foo {
  public static void main(String[] args) throws Exception {
    String newdata = &quot;go forward\ngo left\ntake Pointy Stick\ngo backward\ngo &quot; +
                  &quot;right\nnormal attack\nnormal attack\nquit\n&quot;.trim();
    ByteArrayInputStream testIn = new ByteArrayInputStream(newdata.getBytes());
    System.setIn(testIn);

    boolean includeBug = Boolean.valueOf(args[0]);

    if (includeBug) {

      for(int i=0; i&lt;8; i++) {
        Scanner input = new Scanner(System.in);
        System.out.println(&quot;Read: &quot; + input.nextLine());
      }

    } else {

      Scanner input = new Scanner(System.in);
      for(int i=0; i&lt;8; i++) {
        System.out.println(&quot;Read: &quot; + input.nextLine());
      }

    }
  }
}

When includeBug is true, it creates a new Scanner for each line and crashes like you say. If it's false, it creates a single Scanner and works correctly:

$ javac Foo.java
$ java Foo true
Read: go forward
Exception in thread &quot;main&quot; java.util.NoSuchElementException: No line found
        at java.util.Scanner.nextLine(Scanner.java:1540)
        at Foo.main(Foo.java:17)

$ java Foo false
Read: go forward
Read: go left
Read: take Pointy Stick
Read: go backward
(etc)

答案3

得分: 0

你是在检查它是否有下一行吗?使用扫描器(Scanners),通常你要么必须处理异常(不是我真正喜欢的方式),要么必须使用 hasNextLine() 方法来避免异常。

while (input.hasNextLine()) {
    command = input.nextLine().toLowerCase().trim();
}
英文:

Are you checking if it has a next line? With Scanners, you usually either have to handle the exception (not really something I'd prefer) or you have to use the hasNextLine() method to avoid the exception.

while (input.hasNextLine()) {
     command = input.nextLine().toLowerCase().trim();
}

huangapple
  • 本文由 发表于 2020年9月15日 07:13:13
  • 转载请务必保留本文链接:https://go.coder-hub.com/63892943.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定