1. Overview
1.概述
When we write Java applications to accept users’ input, there could be two variants: single-line input and multiple-line input.
当我们写Java应用程序接受用户的输入时,可能有两种变体:单行输入和多行输入。
In the single-line input case, it’s pretty straightforward to handle. We read the input until we see the line break. However, we need to manage multiple-line user input in a different way.
在单行输入的情况下,处理起来很简单。我们读取输入的内容,直到看到换行。然而,我们需要以不同的方式来管理多行用户输入。
In this tutorial, we’ll address how to handle multiple-line user input in Java.
在本教程中,我们将讨论如何在Java中处理多行用户输入。
2. The Idea to Solve the Problem
2.解决问题的想法
In Java, we can read data from user input using the Scanner class. Therefore, reading data from user input isn’t a challenge for us. However, if we allow users to input multiple lines of data, we should know when the user has given all the data that we should accept. In other words, we need an event to know when we should stop reading from user input.
在Java中,我们可以使用从用户输入端读取数据。因此,从用户输入中读取数据对我们来说并不是一个挑战。然而,如果我们允许用户输入多行数据,我们就应该知道用户何时给出了我们应该接受的所有数据。换句话说,我们需要一个事件来知道我们何时应该停止从用户输入中读取数据。
A commonly used approach is we check the data that the user sends. If the data match a defined condition, we stop reading input data. In practice, this condition can vary depending on the requirement.
一个常用的方法是我们检查用户发送的数据。如果数据符合定义的条件,我们就停止读取输入数据。在实践中,这个条件可以根据需求而变化。
An idea to solve the problem is writing an infinite loop to keep reading user input line by line. In the loop, we check each line the user sends. Once the condition is met, we break the infinite loop:
解决这个问题的一个想法是编写一个无限循环来持续逐行读取用户的输入。在这个循环中,我们检查用户发送的每一行。一旦条件得到满足,我们就打破无限循环。
while (true) {
String line = ... //get one input line
if (matchTheCondition(line)) {
break;
}
... save or use the input data ...
}
Next, let’s create a method to implement our idea.
接下来,让我们创建一个方法来实现我们的想法。
3. Solving the Problem Using an Infinite Loop
3.使用无限循环解决该问题
For simplicity, in this tutorial, once our application receives the string “bye” (case-insensitive), we stop reading the input.
为了简单起见,在本教程中,一旦我们的应用程序收到字符串”bye“(不区分大小写),我们就停止读取输入。
Therefore, following the idea we’ve talked about previously, we can create a method to solve the problem:
因此,按照我们之前谈到的想法,我们可以创建一个方法来解决这个问题。
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;
}
As the code above shows, the readUserInput method reads user input from System.in and stores the data in the userData List.
如上面的代码所示,readUserInput方法从System.in读取用户输入,并将数据存入userData List。
Once we receive “bye” from the user, we break the infinite while loop. In other words, we stop reading user input and return userData for further processing.
一旦我们收到用户的“bye”,我们就会打破无限的while循环。换句话说,我们停止读取用户输入,并返回userData进行进一步处理。
Next, let’s call the readUserInput method in the main method:
接下来,让我们在main方法中调用readUserInput方法。
public static void main(String[] args) {
List<String> userData = readUserInput();
System.out.printf("User Input Data:\n%s", String.join("\n", userData));
}
As we can see in the main method, after we call readUserInput, we print out the received user input data.
正如我们在main方法中看到的,在我们调用readUserInput后,我们打印出了收到的用户输入数据。
Now, let’s start the application to see if it works as expected.
现在,让我们启动该应用程序,看看它是否像预期的那样工作。
When the application starts, it waits for our input with the prompt:
当应用程序启动时,它等待着我们的输入提示。
Please enter your data below: (send 'bye' to exit)
So, let’s send some text and send “bye” at the end:
因此,让我们发送一些文本,并在最后发送”bye“。
Hello there,
Today is 19. Mar. 2022.
Have a nice day!
bye
After we input “bye” and press Enter, the application outputs the user input data we’ve gathered and exits:
在我们输入”bye“并按下Enter后,应用程序输出我们收集的用户输入数据并退出。
User Input Data:
Hello there,
Today is 19. Mar. 2022.
Have a nice day!
As we have seen, the method works as expected.
正如我们所看到的,该方法如预期般运作。
4. Unit Testing the Solution
4.对解决方案进行单元测试
We’ve solved the problem and tested it manually. However, we may need to adjust the method to adapt to some new requirements from time to time. Therefore, it would be good if we could test the method automatically.
我们已经解决了这个问题并进行了手动测试。然而,我们可能需要不时地调整该方法以适应一些新的要求。因此,如果我们能够自动测试该方法,那就更好了。
Writing a unit test to test the readUserInput method is a bit different from regular tests. This is because when the readUserInput method gets invoked, the application is blocked and waiting for user input.
编写测试readUserInput方法的单元测试与常规测试有些不同。这是因为当readUserInput方法被调用时,应用程序被阻塞并等待用户输入。
Next, let’s see the test method first, and then we’ll explain how the problem is solved:
接下来,让我们先看看测试方法,然后再说明问题是如何解决的。
@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);
}
}
Now, let’s walk through the method quickly and understand how it works.
现在,让我们快速浏览一下这个方法,了解它是如何工作的。
At the very beginning, we’ve created a String array inputLines to hold the lines we want to use as the user input. Then, we’ve initialized the expected List, containing the expected data.
在一开始,我们创建了一个String数组inputLines来保存我们想用作用户输入的行。然后,我们初始化了预期 List,包含预期数据。
Next, the tricky part comes. After we backup the current System.in an object in the stdin variable, we’ve reassigned the system standard input by calling the System.setIn method.
接下来,棘手的部分来了。我们在stdin变量中备份了当前的System.in对象后,通过调用System.setIn方法重新分配了系统标准输入。
In this case, we want to use the inputLines array to simulate the user input.
在这种情况下,我们想使用inputLines数组来模拟用户输入。
Therefore, we’ve converted the array to InputStream, a ByteArrayInputStream object in this case, and reassigned the InputStream object as the system standard input.
因此,我们已经将数组转换为InputStream,在这里是一个ByteArrayInputStream对象,并将InputStream对象重新分配为系统标准输入。
Then, we can call the target method and test if the result is as expected.
然后,我们可以调用目标方法,测试结果是否符合预期。
Finally, we shouldn’t forget to restore the original stdin object as the system standard input. Therefore, we put System.setIn(stdin); in a finally block, to make sure it’ll get executed anyway.
最后,我们不应该忘记恢复原来的stdin对象作为系统标准输入。因此,我们将System.setIn(stdin);放在finally块中,以确保它无论如何都会被执行。
It’ll pass without any manual intervention if we run the test method.
如果我们运行测试方法,不需要任何人工干预就能通过。
5. Conclusion
5.总结
In this article, we’ve explored how to write a Java method to read user input until a condition is met.
在这篇文章中,我们已经探讨了如何编写一个Java方法来读取用户的输入,直到满足一个条件。
The two key techniques are:
这两个关键技术是。
- Using the Scanner class from the standard Java API to read user input
- Checking each input line in an infinite loop; if the condition is met, break the loop
Further, we’ve addressed how to write a test method to test our solution automatically.
此外,我们已经解决了如何写一个测试方法来自动测试我们的解决方案。
As always, the source code used in this tutorial is available over on GitHub.
一如既往,本教程中所使用的源代码可在GitHub上获得。