14. 变量作用域,文件输入/输出,以及流¶
14.1. 变量作用域¶
既然您已经熟悉了在各种情况下使用字段、参数和变量,是时候学习用户定义名称确切的可访问位置了。用户定义名称的 作用域 是指该名称可见并可被使用的源代码区域。
14.1.1. 局部范围¶
回顾一下,修改器方法(或常称为“设置器”)用于更改类中私有字段的值。例如:
public class Cat
{
private String color;
// ...
public void setColor(String newColor)
{
color = newColor;
}
}
这个 setColor() 方法同时使用了字段和参数。需要注意的是,这两种变量的使用位置存在差异。变量或方法的 作用域 指的是它在程序中可以被使用的范围。
参数作用域仅限于其声明的方法体。参数是方法头参数列表中声明的局部变量,其初始值由方法调用中的参数指定。例如,如果我们有一个类型为 Cat 的对象 c ,我们可以像这样调用 setColor() 方法:
c.setColor("Black");
当我们编写方法时,我们声明变量 newColor ,当在此处调用该方法时,我们将 s 设置为字符串"Black"。 如果我们想再次调用该方法,我们需要为变量 newColor 提供一个新的值。
c.setColor("Black");
c.setColor("Grey");
这些值不会在方法调用之间传递。
参数的作用域与在方法体最开始声明的变量的作用域相同。一旦执行流离开一个方法,其参数和其他局部变量便不再存在。局部变量的作用域被称为 局部作用域。
备注
Local Variables
局部变量,即方法体中声明的参数和变量,具有 局部作用域,该作用域从定义它们的点开始,一直延伸到定义它们的代码块结束。特别是,参数的作用域是声明它的方法的整个方法体。
从方法外部引用方法的参数或其他局部变量将是语法错误。
14.1.2. 块级作用域¶
声明在方法体内的那些变量的作用域从声明点延伸到声明所在的代码块 块 的末尾。当在方法开头声明局部变量时,它具有上述讨论的局部作用域。
然而,局部变量并不局限于方法的开头,它们的声明可以放在其他地方,这会影响它们的作用域。当涉及像 if 语句或循环这样的控制结构时,作用域可能会更加具体。
public void exampleMethod(int x)
{
if (x % 2 == 0)
{
int value = 4;
}
value = value + 2; // This will not work!
}
在循环或条件等控制结构中花括号( {} )内声明的变量仅存在于这些花括号内。上述方法无法工作,因为变量 value 仅在 if 语句的 true 分支中声明和初始化,当到达标记 if 语句的 true 分支结束的对应闭合花括号时,该变量即不再存在。因此,一旦执行离开声明它的块(这对花括号),其名称便不再可见。任何试图在声明它的花括号之外使用该变量的操作都会导致编译器错误,因为该变量不再可见或可访问——不再“在作用域”内。
对于循环结构也是如此:
for (int i = 0; i < 12; i++)
{
System.out.println(i);
}
i = i + 1; // This will not work!
变量 ``i`` 作为 for 循环的一部分被定义,其作用域是 for 循环的主体——即包围循环主体的花括号。 ``i`` 在 for 循环结束后即不再存在。
为了解决这个问题,你有时会遇到这样的代码:在控制结构 之前 声明一个变量,这样它既可以被控制结构内部访问,也可以被控制结构之后访问。
public void exampleMethod(int x)
{
int value = 0;
if (x % 2 == 0)
{
value = 4;
}
value = value + 2;
}
我们也可以使用 for 循环来做类似的事情:
int i = 0;
for (i = 0; i < 12; i++)
{
System.out.println(i);
}
i = i + 1; // This will work!
14.1.3. 类作用域¶
相比之下,字段和方法的可见性范围涵盖整个类,即 类作用域。它们可以在任何方法体中使用,也可以用于给类级别变量赋值初始值的表达式中。
备注
Class-Level Variables
字段和方法具有类作用域,该作用域贯穿整个类。
14.1.4. 一个常见的误解¶
在声明变量后,人们往往倾向于在引用它时同时使用变量名和变量类型。例如:
public class Cat
{
private String color;
// ...
public void setColor(String newColor)
{
String color = newColor;
}
}
这个设置方法将 不会 更改字段 color 的值。对于 Java 而言,每当变量的类型被包含在内时,你就是在声明一个变量。Java 允许你声明同名变量,只要它们处于不同的作用域中。在上述代码中,有一个名为 color 的字段,其作用域为类级别,且 有一个名为 color 的局部变量,它仅存在于 setColor() 方法之内。
尽管这些变量具有相同的名称和类型,但它们是不同的。修改其中一个不会影响另一个。另一个常见的例子可以在测试中看到。让我们查看我们 Cat 类的假设性测试文件。
public class CatTest
extends TestCase
{
private Cat testCat;
public void setUp()
{
Cat testCat = new Cat();
}
public void test1()
{
testCat.setColor("White");
}
}
这是我们在上一个例子中看到的相同问题。有一个类级别的对象( Cat )被声明为字段( private Cat testCat; )。但是,我们并没有在 setUp() 内部初始化该字段,而是还声明了一个局部变量,也叫做 testCat 。这意味着字段 testCat 将不会被初始化,因为在 setUp() 内部创建的对象被用来初始化该方法中的局部变量,而该局部变量将在方法结束时消失。当我们引用 testCat 变量时(在 test1() 中),我们引用的是从未被初始化的字段,因此它将包含 null 。因此,这个测试将产生一个 NullPointerException 。
幸运的是,这个问题很容易解决。一旦声明了变量,我们只需要通过变量的名称来引用它。
public class CatTest
extends TestCase
{
private Cat testCat;
public void setUp()
{
testCat = new Cat();
}
public void test1()
{
testCat.setColor("White"); // this won't work!
}
}
这段代码将不会报错。字段 testCat 仍然声明在任何方法之外,因此它具有类级作用域。但这次,它在我们的 setUp() 方法中正确初始化,并且 setUp() 在每次测试之前运行。这意味着在 test1() 中, testCat 将引用一个 Cat 对象,而不是值 null 。
14.1.5. 关于命名的说明¶
如上所述,当变量在不同的作用域中声明时,Java 可以处理具有相同名称和类型的两个变量。这也会导致混淆。例如,我们可以有两个名为 color 的 String 变量。一个是字段,一个是参数。
public class Cat
{
private String color;
// ...
public void setColor(String color)
{
color = color;
}
}
这段代码可以编译通过,但不建议使用这种命名约定。这是因为不清楚字段 color 是否被设置为参数 color ,或者反之,亦或是其他完全不同的情况。让我们通过添加几个打印语句来看看这里发生了什么:
public class Cat
{
private String color;
public Cat()
{
this.color = "Black";
}
public void setColor(String color)
{
color = color;
System.out.println(this.color);
System.out.println(color);
}
}
在此示例中,每当我们创建一个新的 Cat 对象时,字段 color 的值首先被设置为"Black"。当我们运行 setColor("Green") 时,我们会看到打印语句中出现一个有趣的结果:
首先打印出的内容是 this.color 。我们看到它是"Black"。字段的值并没有变为"Green"!这意味着当我们写入 color = color 时,我们知道颜色字段不在赋值运算符的左侧。
因此,人们可能会认为参数 color 是赋值运算符左侧的值。 这意味着该参数已从"Green"变为"Black"。 然而,我们的第二个打印语句表明并非如此。 当我们打印出参数 color 时,我们看到它仍然是"Green"。 这意味着字段 color 也不在等号的右侧!
在此代码中,我们将参数变量 color 设置为自身——这意味着什么也没变!
通常,避免此类混淆的最佳方法是像我们最初做的那样,给变量赋予不同的名称:
public void setColor(String newColor)
{
color = newColor;
}
或者,如果由于某种原因你 必须 在两个不同的作用域级别使用相同的变量名,使用修饰符 this 将有助于澄清你指的是哪个变量:
public void setColor(String color)
{
this.color = color;
}
现在,字段 color 位于赋值运算符的左侧,而参数 color 位于右侧。因此,如果我们运行 setColor("Green"); ,字段 color 将从“黑色”变为“绿色”。
您可能在某些设置方法或构造函数中看到这种惯例,程序员有意将参数名与字段名设为相同,以表明参数是将被存储在字段中的值。在使用这种方法时,必须在引用字段名时始终将 this. 作为前缀,否则该名称的所有使用都将仅指代参数。
14.2. 总结范围概念¶
14.3. 您对课程评分政策的看法¶
请完成以下调查。该调查包含关于您对本课程评分政策意见以及您如何学习本课程的问题。我们将使用这些信息更好地了解这些政策如何影响您,以便我们能够改进本课程。
在调查的最后,您将被告知是否同意您的答案和课程数据用于关于评分政策的研究。 如果您同意您的数据用于该研究,则不需要您做额外的工作。 完成调查应少于 30 分钟。 您的课程讲师将在您完成课程并提交最终成绩后才知道您是否同意参与。 无论您是否同意参与研究或拒绝回答部分或所有问题,提交此调查您都将获得作业学分。
CS 1114 Grading Policies Survey 2请回答下方问题,您的调查提交将用于信用验证。
14.4. Java 输入与输出¶
我们一直使用 System.out.println() ,但你可能没想过它的含义。 System 是一个类,它提供与程序运行的“系统”或环境相关的方法。它还提供 System.out ,这是一个特殊字段,它引用一个提供用于显示输出方法的对象,包括 println() 。事实上,我们可以使用 System.out.println() 来显示 System.out 的值:
System.out.println(System.out);
结果是:
java.io.PrintStream@685d72cd
该输出表明, System.out 指向一个 PrintStream 对象,该对象定义在名为 java.io 的包中。包是相关类的集合; java.io 包含用于"输入和输出"的类。
14.4.1. 基本输入与输出概念¶
Java 提供了一个用于管理各种形式数据输入输出的广泛类库。在 Java 中,任何 I/O 的源或目标都被视为“流”或字节或字符的序列。为了执行输出,我们将字节或字符插入流中。为了执行输入,我们从流中提取字节或字符。即使键盘上输入的字符(如果我们把它们看作按键序列)也可以表示为流。
输入 操作被描述为从流中读取数据的三步过程:
打开流
按顺序从流的前端读取数据项
关闭流
输出 操作被描述为以类似三步过程的方式写入流:
打开流
按顺序将数据写入流的末尾
关闭流。
要使用 Java 的输入/输出类,请确保除了导入任何其他必要的包之外,还要导入 java.io 包和 java.util 包:
import java.io.*;
import java.util.*;
14.5. 使用 PrintWriters 输出¶
14.5.1. 打开输出流¶
在本类中,我们只处理文本、可读的输出。我们用于生成输出的主要类是 Java 的 PrintWriter 类,来自 java.io 包。要创建一个 PrintWriter,我们将使用 VT 学生包中 IOHelper 类的一个实用方法( import student.*;):
PrintWriter outStream = IOHelper.createPrintWriter("output.txt");
这一行声明了一个新变量, outStream ,并创建了一个新的 PrintWriter 对象,该对象将输出发送到文件系统中一个全新的文件。如果项目目录中已存在一个名为该文件的文件,则在创建具有相同名称的新空文件之前将其删除。
PrintWriter 对象提供格式化和转换操作。 PrintWriter 对象设计用于将其输出发送到流。它并不知道(或关心)该流是否连接到磁盘文件、网络连接或其他设备。 IOHelper 类提供了创建 PrintWriter 对象的若干其他方法,包括向现有文件追加内容而非覆盖它的方法,或连接到控制台以进行输出的流。
14.5.2. 向输出流写入¶
PrintWriter 对象提供的三种基本方法几乎涵盖了本课程中你所需的所有输出功能:
<stream>.print(<value>);将指定的 <value> 写入给定的 <stream>。实际上,有许多此方法的版本,支持您可能想要打印的每种 <value> 类型。<stream>.println(<value>);将指定的 <value> 写入给定的 <stream>,随后写入一个“行终止符”以标记当前行的结束(Java 会根据当前操作系统的文本文件格式约定写入适当的行终止字符序列)。与print()一样,你可以向println()提供任意类型的值。你甚至可以在完全不给出任何参数的情况下调用println(),例如,在之前的若干print()消息之后终止当前行。<stream>.write(<value>);写入一个由整数 <value> 指定的单个字符。此操作最常用于逐个字符产生输出,而不是以更大的块产生。然而,如果你向write()传递一个完整的String值而不是一个int值,那么整个字符串将被写入到PrintWriter(),就像你使用了print()一样。
例如:
outStream.print("This is a message, and ");
outStream.println("these words appear on the same line as those above");
outStream.println(100 / 2); // prints the value "50"
outStream.write(65); // writes the letter 'A', whose ASCII code is 65
14.5.3. 关闭流¶
一旦您完成了在给定流上执行的所有操作,该流应该被关闭。关闭流会释放操作系统用于连接和与流通信的资源,并确保您写入流的所有缓冲数据被刷新到相关的物理设备(如果有的话)。
关闭流很简单:
outStream.close();
您应该以这种方式同时关闭输入流和输出流。在许多简单的程序中,一个经验法则是创建流的流方法也应该是负责关闭该流的方法。
14.5.4. 一个完整的输出示例¶
我们可以将这些部分组合起来,以展示如何生成输出到文件,例如。假设我们想要创建一个名为 output.txt 的文件,包含我们程序的某些输出。我们可以像这样在一个方法中完成它(不要忘记在你的类中导入 java.io.* ):
public void printResultFile(int result)
{
PrintWriter out = IOHelper.createPrintWriter("output.txt");
out.println("This is the first line of output.");
out.print("The result is: ");
out.print(result);
out.println();
out.close();
}
如果传入特定参数,例如 printResultFile(42); ,该方法将在您的 BlueJ 项目目录中生成一个名为 output.txt 的文件,其中包含以下行:
This is the first line of output.
The result is: 42
在其他时候,当需要产生大量输出时,您可能希望将所有的 println() 调用放在一个或多个其他方法中。然后您可以将一个 PrintWriter 对象作为参数传递,如下例所示:
public void printResultFile()
{
PrintWriter out = IOHelper.createPrintWriter("output.txt");
printHeader(out);
printData(out);
out.close();
}
public void printHeader(PrintWriter outStream)
{
outStream.println("This is the output for ...");
// other output commands go here.
}
public void printData(PrintWriter outStream)
{
outStream.print(/* ... */);
// more, as needed ...
}
14.5.5. 使用 System.out 输出¶
发现向终端打印是一种非常常见的操作,因此 Java 为此提供了一个预先初始化的输出流,称为 System.out 。 System.out 的优点在于它已经声明完毕且始终可供使用,而您的程序无需负责关闭它。因此,您可以在任何位置直接调用 print() 、 println() 或 write() 于 System.out 。
System.out.println("beginning the code ...");
...
if (someCondition())
{
System.out.println("someCondition() is true");
x = ...;
System.out.println("x = " + x);
}
else
{
System.out.println("someCondition() is false");
y = ...;
System.out.println("y = " + y);
}
在此之上,请注意加号运算符( + )是如何被用来将文本字符串与另一个值结合以生成更大消息的。这是 Java 的一个很好的特性——加号运算符通过将另一个值转换为人类可读的字符串表示来“连接”两个字符串,即一个接一个地放置。此外,当你将字符串与任何其他类型的值连接时,该值首先通过调用其 toString() 方法转换为人类可读的字符串表示。
因此,本课程输出的一些建议如下:
当您只想在终端窗口中生成简单的消息以帮助调试代码问题时,请使用
System.out。当您只想交互式地提示用户输入一些值时,使用
System.out。当您的程序需要在文件中生成一系列输出行时,使用
PrintWriter。当您的程序需要生成一系列输出行,这些行可能输出到终端窗口或文件时,请编写一个或多个使用
PrintWriter作为参数的方法。您可以始终调用此类方法,并提供一个由System.out流产生的PrintWriter,以在屏幕上产生输出(参见IOHelper.createConsoleWriter()方法)。或者,您可以传递一个连接到文件的PrintWriter(甚至连接到互联网套接字以与另一台机器上的另一个程序通信!)。
14.6. 使用扫描器输入¶
14.6.1. 为输入打开流¶
我们用来读取输入的主要类是 Java 的 Scanner 类,来自 java.io package 。创建一个 Scanner 很简单:
Scanner inStream = IOHelper.createScanner("input.txt");
本行声明了一个新名称, inStream ,并创建一个 Scanner 对象,该对象从名为该文件的字符中读取。 createScanner() 方法使用相对于项目目录的路径名称打开文件,因此名为 input.txt 的文件应位于该处。如果您愿意,可以提供完全限定路径名称而不是相对路径名称。
java.io 包提供了一个丰富的类继承层次结构,用于从文本文件读取数据。 Scanner 类被创建以简化文本输入,因此优于其他类。
14.6.2. 从输入流读取¶
许多由 Scanner 对象提供的方法提供了本课程所需的大多数输入功能:
<scanner>.hasNext();返回true,如果此扫描器在其输入中还有另一个标记。<scanner>.next();查找并返回下一个完整标记(默认情况下,返回下一个空白分隔的字符串对象,如下一行或下一个制表符分隔的单词)。如果不再可用标记,则抛出NoSuchElementException(即,您已经到达输入末尾)。<scanner>.hasNextLine();返回true,如果此扫描器在其输入中还有另一行。<scanner>.nextLine();查找并返回下一行。如果不再有更多令牌,则抛出NoSuchElementException(即,您已经到达输入末尾)。<scanner>.hasNext<PrimitiveType>();可以被<PrimitiveType>、double、float、int等替换。如果此扫描器在其输入中还有另一个标记,并且该标记可解释为<PrimitiveType>类型的值,则返回true。<scanner>.next<PrimitiveType>();他<PrimitiveType>可以被double、float、int等替代。该方法将输入的下一个标记扫描为<PrimitiveType>,并返回对应的<PrimitiveType>值。如果下一个标记不匹配<PrimitiveType>,或扫描到的值超出范围,则抛出InputMismatchException。如果没有更多标记可用,则抛出NoSuchElementException。<scanner>.useDelimiter(String pattern);默认情况下,空白(空格、制表符或换行符)用作分隔符,将输入分割成令牌以返回。此方法允许用户设置分隔符字符为任何他们想要的字符,用于拆分输入。逗号是另一种常用的分隔符,因为表格或数据通常存储在称为 CSV(逗号分隔值)的文件中。<scanner>.close();关闭扫描仪以释放扫描仪占用的系统资源。
要使用这些方法,通常您会逐行处理输入,然后在该行中扫描所需的标记。
例如:
Scanner inStream = IOHelper.createScanner("input.txt");
// if NOT at the end of the stream, more input is available
if (inStream.hasNextLine())
{
// Get an entire line
String thisLine = inStream.nextLine();
// Create a scanner to process the line
Scanner line = new Scanner(thisLine);
// Check for the next whitespace delimited int
if (line.hasNextInt())
{
System.out.println(line.nextInt());
}
}
inStream.close();
注意,在将每个输入提取之前都会检查其存在性,以避免异常。
此外,如果您之前用其他语言编写过程序,请注意 Java 中的字符使用 unicode 编码,这是一种 16 位字符代码。其他语言程序员可能更熟悉 ASCII,即美国信息交换标准代码,这是一种 7 位字符代码。幸运的是,unicode 的前 128 个代码等同于 ASCII character set。对于美国用户,因此可以在读取和写入字符时自由使用 ASCII 值而不会出错,尽管这种方法不能直接扩展到为国际受众编写的程序。
Scanner 类可用于从任何输入流中读取数据,包括文件、通过终端窗口读取键盘输入,甚至 URL。例如,要从键盘读取:
Scanner keyBoard = IOHelper.createKeyboardScanner();
System.out.print("Enter your name: ");
// Prompt the user String name = keyBoard.nextLine();
System.out.println("Hello " + name); // Echo input
在执行交互式键盘输入时,无需检查下一个标记是否存在。扫描器会自动阻塞(即等待)用户输入。
如果知道 URL,还可以使用 Scanners 从 Web 上公开可用的文件中读取数据:
Scanner inWebFile = IOHelper.createScannerForURL(
"http://server.subdomain.domain/dir/file.txt");
while (inWebFile.hasNextLine())
{
String line = inWebFile.nextLine();
System.out.println(line); // Echo input
}
inWebFile.close();
14.7. 一个完整的输入示例¶
我们可以将这些部分组合起来,展示如何一次读取文件中的一个字符,例如。假设我们要读取一个名为 input.txt 的文件中的字符。我们可以使用一个方法来实现(不要忘记在你的类中 import java.io.* 和 java.util.* ):
public void readChars()
{
Scanner in = IOHelper.createScanner("input.txt");
// while NOT at the end of the stream, more input is available
while (in.hasNextLine())
{
String thisLine = in.nextLine(); // Get an entire line
for (int index = 0; index < thisLine.length(); index++)
{
char ch = thisLine.charAt(index);
System.out.print(ch);
}
System.out.println();
}
in.close();
}
在其他时候,当需要产生大量输出时,您可能希望将所有的 read() 调用放在一个或多个其他方法中。然后您可以将一个 Scanner 对象作为参数传递:
public void processInputFile()
{
Scanner in = IOHelper.createScanner("input.txt");
readHeader(in);
readData(in);
in.close();
}
public void readHeader(Scanner inStream)
{
String nextLine = null;
if (inStream.hasNextLine())
{
nextLine = inStream.nextLine();
// other input commands go here.
}
}
public void readData(Scanner inStream)
{
String nextLine = null;
if (inStream.hasNext() )
{
nextLine = inStream.nextLine();
// more, as needed ...
}
}
14.8. 一个完整的输入/输出示例¶
通常,有必要将来自某个源的数据读取和写入某个目的地的过程结合起来。这里有一个简单的例子,它逐字符复制输入文件:
import cs1705.*;
import java.io.*;
import java.util.*;
// -------------------------------------------------------------------------
/**
* Shows how to read/write a file one character at a time.
* @author Dwight Barnette
* @version 2006.03.09
*/
public class CopyFileByLine
{
// ----------------------------------------------------------
/**
* Copy the source file to the specified destination file.
* @param fromFile the name of the file to copy from
* @param toFile the name of the file to copy to
*/
public void copyFile(String fromFile, String toFile)
{
Scanner source = IOHelper.createScanner(inFile);
PrintWriter dest = IOHelper.createPrintWriter(toFile);
while (source.hasNextLine())
{
String thisLine = source.nextLine();
for (int index = 0; index < thisLine.length(); index++)
{
char ch = thisLine.charAt(index);
dest.print(ch);
}
dest.println();
}
source.close();
dest.close();
}
}
14.9. 测试基于 I/O 的操作¶
当涉及到测试时,请记住为你在解决方案中编写的每个方法编写一个或多个测试用例。最好在你编写该方法本身之前(或同时)编写这些测试,而不是等到你的代码工作后才开始测试。随着你工作的程序越来越大,培养一种技能变得很重要,即说服自己你已经编写的部分按你的意图工作,即使完整的解决方案尚未完成。
对于直接操作控制台来读取输入或产生输出的程序进行测试,似乎很困难,因为很难“断言”屏幕上应该显示什么内容。此外,你总是需要亲自“输入”所需的输入序列。
要完全自动化这些测试,请避免编写使用 System.out 或从外部源读取的测试。相反,只需创建一个 Scanner ,将其作为测试用例的一部分,从固定的输入字符串中读取。对于输出,创建一个 PrintWriter ,使其能够向 String 对象写入,而不是向控制台写入。
为了使这些任务变得容易,所有测试用例继承的 TestCase 基类提供了一些辅助方法:
setIn(<contents>);接受一个字符串并使用它来创建一个Scanner供您的测试用例作为输入使用。扫描器在每个测试用例之前会自动清除,因此如果您希望所有测试用例使用相同的输入序列,可以在setUp()中调用此方法。in();返回当前Scanner被用于输入。您可以结合setIn()在测试用例中为您的基于输入的方法设置输入流。扫描器会在每个测试用例开始时自动清除。out();返回一个PrintWriter,可用于输出。该PrintWriter捕获其自身的所有输出,以便稍后在断言中使用,并在每个测试用例前重置其内容。
例如,考虑以下测试方法(假设您的文本夹具包含一个由某个 DoIt 类创建的 doIt 对象,该类提供一个名为 processSomeInput() 的方法,该方法接受一个 Scanner 参数):
public void testProcessSomeInput()
{
// set up the input stream
setIn("some test input");
// run the method to get results
doIt.processSomeInput(in());
// test that the result is what was expected
assertThat( ... );
}
假设存在一个向 PrintWriter 写入的 produceOutput() 方法:
public void testProcessSomeInput()
{
// run the method to get results
doIt.produceOutput(out());
// test that the result is what was expected
assertThat("what I expect").isEqualTo(out().getHistory());
}
最后,您甚至可以在同一时间处理输入和输出:
public void testProcessSomeInput()
{
setIn("some test input");
// run the method to get results
doIt.processSomeStuff(in(), out());
// test that the result is what was expected
assertThat("output I want").isEqualTo( out().getHistory()));
}
TestCase 基类提供了设置 System.in 或从 System.out 获取历史记录的类似方法。有关更多详情,请参阅 TestCase 的 javadoc。
