1. 概述

当我们编写Java应用程序来接受用户输入时,有两种可能:单行输入和多行输入。

对于单行输入,处理起来相对直接,我们一直读取直到遇到换行符。然而,我们需要采用不同的方式管理多行用户输入。

在这个教程中,我们将讨论如何在Java中处理多行用户输入。

2. 解决问题的思路

在Java中,我们可以使用Scanner类从用户输入中读取数据。因此,从用户输入读取数据对我们来说并不困难。然而,如果我们允许用户输入多行数据,我们就需要知道何时停止接收用户输入。换句话说,我们需要一个事件来知道何时停止从用户输入中读取。

一个常见的做法是检查用户发送的数据。如果数据符合定义的条件,我们就停止读取输入数据。实际上,这个条件可以根据需求进行调整。

解决问题的一个想法是使用无限循环,逐行读取用户输入。在循环中,我们检查用户发送的每一行。一旦满足条件,我们就跳出无限循环:

while (true) {
    String line = ... //get one input line
    if (matchTheCondition(line)) {
        break;
    }
    ... save or use the input data ...
}

接下来,我们将创建一个方法来实现我们的想法。

3. 使用无限循环解决这个问题

为了简化,在这个教程中,一旦我们的应用程序接收到字符串“bye”(不区分大小写),我们就停止读取输入

因此,按照之前讨论的想法,我们可以创建一个方法来解决问题:

public static List<String> readUserInput() {
    List<String> userData = new ArrayList<>();
    System.out.println("Please enter your data below: (send 'bye' to exit) ");
    Scanner input = new Scanner(System.in);
    while (true) {
        String line = input.nextLine();
        if ("bye".equalsIgnoreCase(line)) {
            break;
        }
        userData.add(line);
    }
    return userData;
}

如上代码所示,readUserInput方法从System.in中读取用户输入,并将数据存储在userData列表中。

一旦我们从用户那里收到“bye”,我们就跳出无限的while循环。换句话说,我们停止读取用户输入,并返回userData进行进一步处理。

接下来,在main方法中调用readUserInput方法:

public static void main(String[] args) {
    List<String> userData = readUserInput();
    System.out.printf("User Input Data:\n%s", String.join("\n", userData));
}

如我们在main方法中看到的那样,在调用readUserInput之后,我们打印出收到的用户输入数据。

现在,让我们启动应用程序,看看它是否按预期工作。

当应用程序启动时,它会提示等待我们的输入:

Please enter your data below: (send 'bye' to exit)

所以,让我们发送一些文本,最后发送“bye”:

Hello there,
Today is 19. Mar. 2022.
Have a nice day!
bye

输入“bye"并按下回车后,应用程序输出我们收集的用户输入数据并退出:

User Input Data:
Hello there,
Today is 19. Mar. 2022.
Have a nice day!

正如我们所见,该方法按预期工作。

4. 单元测试解决方案

我们已经解决了问题并进行了手动测试。然而,随着时间的推移,我们可能需要调整方法以适应新的需求。因此,如果我们能自动测试方法就更好了。

为测试readUserInput方法编写单元测试与常规测试有所不同。这是因为readUserInput方法被调用时,应用程序会被阻塞并等待用户输入

首先,让我们看看测试方法,然后解释它是如何工作的:

@Test
public void givenDataInSystemIn_whenCallingReadUserInputMethod_thenHaveUserInputData() {
    String[] inputLines = new String[]{
        "The first line.",
        "The second line.",
        "The last line.",
        "bye",
        "anything after 'bye' will be ignored"
    };
    String[] expectedLines = Arrays.copyOf(inputLines, inputLines.length - 2);
    List<String> expected = Arrays.stream(expectedLines).collect(Collectors.toList());

    InputStream stdin = System.in;
    try {
        System.setIn(new ByteArrayInputStream(String.join("\n", inputLines).getBytes()));
        List<String> actual = UserInputHandler.readUserInput();
        assertThat(actual).isEqualTo(expected);
    } finally {
        System.setIn(stdin);
    }
}

现在,让我们快速浏览一下方法,理解它是如何工作的。

一开始,我们创建了一个String数组inputLines来存储我们想要用作用户输入的行。然后,我们初始化了一个包含预期数据的expected列表。

接下来,关键部分来了。在备份当前的System.in对象到stdin变量后,我们通过调用System.setIn方法重置了系统标准输入。

在这种情况下,我们希望使用inputLines数组模拟用户输入

因此,我们将数组转换为InputStream(在这个例子中是ByteArrayInputStream),并将InputStream对象重新分配给系统标准输入。

然后,我们可以调用目标方法,测试结果是否符合预期。

最后,我们不应忘记恢复原始的stdin对象作为系统标准输入。因此,我们在finally块中放置System.setIn(stdin),确保无论如何都会执行。

如果不进行任何手动干预,运行测试方法就会通过。

5. 总结

在这篇文章中,我们探讨了如何编写Java方法,直到满足某个条件才停止读取用户输入。

关键技巧包括:

  • 使用Java标准库中的Scanner类读取用户输入
  • 在无限循环中检查每行输入;如果满足条件,跳出循环

此外,我们还讨论了如何编写测试方法来自动测试我们的解决方案。

如往常一样,本文使用的源代码可以在GitHub上找到。