• 初识Java 14-1 测试


    目录

    测试

    单元测试

    JUnit

    测试覆盖率

    前置条件

    断言

    Java提供的断言语法

    Guava提供的更方便的断言

    契约式设计中的断言

    DbC + 单元测试

    Guava中的前置条件


    本笔记参考自: 《On Java 中文版》


    测试

    ||| 如果没有经过测试,代码就不可能正常工作。

            Java大体上可以被称为一门静态类型语言。静态类型检查是一种非常有限的测试类型,它仅仅保证代码的语法和基本类型没有问题,但却不能保证我们的代码能够满足程序的目标。

            为此,就需要程序员来进行代码验证。第一步,我们需要创建测试,检查我们的代码行为是否满足了我们的目标。

    单元测试

            单元测试是一个将集成测试构建到我们所创建的所有代码中的过程。并在每次测试的时候运行这些测试。这种做法可以用来检测语义错误

            单元测试通常是一小段代码。通过为每一个类构建自己的测试来检查它所有方法的行为。Java的垃圾收集和类型检查等功能共同构成了Java的安全网。将单元测试集成到构建过程中,我们就可以扩展这个安全网。从而加快开发速度。

        除此之外,还有“系统”测试,用来检测已完成的程序是否能够满足最终要求。

    项目构建

            通过一些项目构建工具,我们可以把源代码生成可执行应用程序的过程自动化。这种工具可以帮助我们管理项目的外部依赖包、项目编译和打包等工作。这里简单介绍几种Java的项目构建工具(参考自默 语的博客):

    1. Ant
      1. 优点:灵活、速度快。
      2. 缺点:学习路线较为陡峭,配置复杂,难以适应大型项目。
    2. Maven
      1. 优点:功能强大,可自动下载依赖包并构建项目。
      2. 缺点:项目构建僵化,配置不够灵活,不适合小型项目。
    3. Gradle
      1. 优点:灵活、可扩展,支持远程仓库。可以根据需要自定义任务和行为。
      2. 缺点:学习成本高,版本兼容性差等。

    JUnit

            JUnit是一个开源的Java测试框架,可用于编写可靠、高效的测试(并且可用于创建自动测试)。在编写单元测试时,它是一个很好的工具。

            直至笔者写下本笔记的时候,Junit已经更新到了JUnit 5版本,可以在GitHub上找到这个项目:

    JUnit 5icon-default.png?t=N7T8https://github.com/junit-team/junit5接下来将会参考JUnit 5的指导手册,做一些必要的介绍(但不会包括如何下载和配置JUnit,已经有许多人写过很出色的博客来说明这部分了)

            JUnit需要运行在JVM上,而JUnit 5在运行时的最低配置是JDK 8(不过也可以用它来测试更早版本的程序)。JUnit提供了许多用于配置测试并扩展框架的注解,可以在官方提供的文档中进行查看,这里仅仅简单介绍一些我们会用到的:

    注释描述
    @Test表明该方法是测试方法。在JUnit 5中,该注释没有任何属性。
    @TestFactory表明该方法是一个被用于动态测试的测试工厂。
    @BeforeAll拥有该注解的方法会在任何测试执行之前运行一次,这种方法必须是静态的。
    @AfterAll拥有该注解的方法会在任何测试执行之后运行一次,这种方法必须是静态的。
    @BeforeEach通常用于创建和初始化一组公共对象,并在每次测试之前运行。
    @AfterEach拥有该注解的方法会在测试结束之后运行。通常用于每次测试之后的清理(如恢复static成员,关闭文件、数据库或网络连接等)。

    【例子:简单的测试】

            首先写一段简单的代码:

    1. package validating;
    2. import java.util.ArrayList;
    3. public class CountedList extends ArrayList {
    4. private static int counter = 0;
    5. private int id = counter++;
    6. public CountedList() {
    7. System.out.println("CountedList #" + id);
    8. }
    9. public int getId() {
    10. return id;
    11. }
    12. }

            接下来编写测试代码对其进行检查。标准的做法是将测试代码放在一个独立的包中:

    1. package validating.tests;
    2. import org.junit.jupiter.api.*;
    3. import validating.CountedList;
    4. import java.util.Arrays;
    5. import java.util.List;
    6. import static org.junit.jupiter.api.Assertions.*;
    7. class CountedListTest {
    8. private CountedList list;
    9. // BeforeAll和AfterAll修饰的方法都是静态的
    10. @BeforeAll
    11. static void beforeAllMsg() {
    12. System.out.println(">>> 开始对CountedList的测试");
    13. }
    14. @AfterAll
    15. static void afterAllmsg() {
    16. System.out.println();
    17. System.out.println(">>> 结束对CountedList的测试");
    18. }
    19. @BeforeEach
    20. public void initialize() {
    21. System.out.println();
    22. list = new CountedList();
    23. System.out.println("对id为#" + list.getId() + "的单元进行设置");
    24. for (int i = 0; i < 3; i++)
    25. list.add(Integer.toString(i));
    26. }
    27. @AfterEach
    28. public void cleanup() {
    29. System.out.println("对id为#" + list.getId() + "的单元进行清理");
    30. }
    31. @Test
    32. public void insert() {
    33. System.out.println("运行测试:输入");
    34. assertEquals(list.size(), 3); // 断言方法
    35. list.add(1, "插入");
    36. assertEquals(list.size(), 4); // 断言方法
    37. assertEquals(list.get(1), "插入");
    38. }
    39. @Test
    40. public void replace() {
    41. System.out.println("运行测试:替换");
    42. assertEquals(list.size(), 3);
    43. list.set(1, "替换");
    44. assertEquals(list.size(), 3);
    45. assertEquals(list.get(1), "替换");
    46. }
    47. // 只需要在测试中运行的辅助方法compare
    48. // 没有@Test之类的注解,不会自动运行
    49. private void compare(List lt, String[] strs) {
    50. assertArrayEquals(lt.toArray(new String[0]), strs);
    51. }
    52. @Test
    53. public void order() {
    54. System.out.println("运行测试:检查顺序");
    55. compare(list, new String[]{"0", "1", "2"});
    56. }
    57. @Test
    58. public void remove() {
    59. System.out.println("运行测试:删除");
    60. assertEquals(list.size(), 3);
    61. list.remove(1);
    62. assertEquals(list.size(), 2);
    63. compare(list, new String[]{"0", "2"});
    64. }
    65. @Test
    66. public void addAll() {
    67. System.out.println("运行测试:放入多项数据");
    68. list.addAll(Arrays.asList(new String[]{"许多", "项", "数据"}));
    69. assertEquals(list.size(), 6);
    70. compare(list, new String[]{"0", "1", "2", "许多", "项", "数据"});
    71. }
    72. }

    程序执行的结果是:

            JUnit使用@Test来标注测试方法。在这些方法中,我们可以执行任何所需的操作。并且可以使用JUnit的断言方法(这些方法都以“asset”开头)来验证测试的正确性。如果需要,可以在文档中找到这些断言方法的说明。

    测试覆盖率

    ||| 测试覆盖率,也称为代码覆盖率。是衡量代码库的测试百分比,百分比越高,测试覆盖率越大。

            可能会存在这样一种误解,必须追求覆盖率的100%:

    这是有问题的,因为这个数据并不是衡量测试有效性的合理标准。有时,即使我们测试了所有需要测试的内容,测试覆盖率也只会达到60%~70%。换言之,如果盲目追求100%的覆盖率,就会浪费大量的时间。

            一般情况下,测试覆盖率只作为粗略的衡量标准。注意,必要依赖覆盖率来获取测试质量的相关信息。

    前置条件

            前置条件的概念来自契约式设计,并且使用了基本的断言机制来实现。在了解契约式设计之前,需要先介绍一下断言的基本概念。

    断言

            断言通过验证程序执行期间是否满足某些条件来提高程序的稳健性。断言可以应用于判断数值的范围、参数的有效性等多种场景。

    Java提供的断言语法

            有许多编程结构可以用来模拟断言的效果。Java本身也提供了两种现成的断言语句:

    1. assert boolean-expression;
    2. assert boolean-expression: information-expression;

    断言会判断表达式的值是否为true。若不是true,断言会产生一个AssertionError异常(这个异常是Throwable的子类,因此不需要指定异常规范)。

            注意:第一种断言形式产生的异常不会包含boolean-expression的任何信息。

    【例子:第一种断言形式】

    1. // 需要在运行时显示地启动断言、
    2. // 启动断言最简单的方法是在运行程序时使用-ea标志
    3. public class Assert1 {
    4. public static void main(String[] args) {
    5. assert false;
    6. }
    7. }

            程序执行的结果是:

            Java的断言默认不打开,因此我们必须在运行程序时显式地启用断言。最简单的方法是使用-ea标志(也可拼写为-enableassertions)。这将在运行程序时执行任何断言语句。IDEA可以通过修改运行配置添加该标志:

            而若使用第二种的断言,就可以在异常栈中生成一个有用的消息:

    【例子:第二种断言形式】

    1. public class Assert2 {
    2. public static void main(String[] args) {
    3. assert false :
    4. "这是一条信息,用来说明发生了什么"; // information-expression
    5. }
    6. }

             程序执行的结果是:

            information-expression可以是任何类型的对象。但通常,我们会构造一个更复杂的字符串,其中会包含与失败断言有关的对象的信息。

        可以根据类名或包名打开或关闭断言,详见JDK文档

            还有另一种控制断言的方式:以编程的方式操作ClassLoader对象。ClassLoader中有几种方法允许动态启用和禁用断言。

    【例子:通过ClassLoader开启断言】

    1. public class LoaderAssertions {
    2. public static void main(String[] args) {
    3. ClassLoader.getSystemClassLoader()
    4. .setDefaultAssertionStatus(true); // 该方法会为其之后加载的所有类设置断言状态
    5. new Loaded().go();
    6. }
    7. }
    8. class Loaded {
    9. public void go() {
    10. assert false : "Loaded.go()";
    11. }
    12. }

            程序执行的结果是:

            这样就不需要使用-ea标志了。在独立交付产品时,可能会需要使用独立脚本来配置其他启动参数,以保证用户无论如何都可以启动程序。

            另外,也可以在程序运行时再决定是否启用断言,可以使用静态语句做到这一点。

    【例子:使用静态语句控制断言】

    1. static {
    2. boolean assertionsEnabled = false;
    3. assert assertionsEnabled = true; // 利用赋值的副作用进行重新赋值
    4. if (!assertionsEnabled)
    5. throw new RuntimeException("断言已禁用");
    6. }

            由于赋值的返回值是赋值操作符右边的值,因此可以利用这一点控制断言的开启。

    ------

    Guava提供的更方便的断言

            Guava团队提供了一个Verify类来替代Java原生的断言,这个类提供了始终启动的替换断言(Guava是谷歌提供的第三方库,可以在GitHub上找到这个库)。他们建议静态导入Verify类:

    【例子:Guava中的断言】

    1. import com.google.common.base.VerifyException;
    2. import static com.google.common.base.Verify.*;
    3. public class GuavaAssertions {
    4. public static void main(String[] args) {
    5. verify(1 + 1 == 2);
    6. try {
    7. verify(1 + 1 == 4);
    8. } catch (VerifyException e) {
    9. System.out.println(e);
    10. }
    11. try {
    12. verify(1 + 1 == 4, "算错了");
    13. } catch (VerifyException e) {
    14. System.out.println(e.getMessage());
    15. }
    16. try {
    17. verify(1 + 1 == 4, "算错了:%s", "不是4");
    18. } catch (VerifyException e) {
    19. System.out.println(e.getMessage());
    20. }
    21. String s = "";
    22. s = verifyNotNull(s);
    23. s = null;
    24. try {
    25. verifyNotNull(s);
    26. } catch (VerifyException e) {
    27. System.out.println(e.getMessage());
    28. }
    29. try {
    30. verifyNotNull(s, "不应该为空:%s", "arg s");
    31. } catch (VerifyException e) {
    32. System.out.println(e.getMessage());
    33. }
    34. }
    35. }

            程序执行的结果是:

            verify()verifyNotNull()都可以提供错误信息。但推荐使用verifyNotNull(),因为verify()提供的信息过于笼统了。

    ------

    契约式设计中的断言

            契约式设计(DbC),即通过保证对象遵循某些规则来创建稳健的程序。这些规则由要解决问题的性质决定,而且超出了编译器可以验证的范围(也有说法认为,接口的本质就是契约)

            断言可以创建一种非正式的DbC编程风格。

            DbC假定服务提供者和服务消费者之间存在着明确的合同。接口分隔了提供者和消费者,当客户调用某些公共方法时,他们会期望调用特定的行为:对象中状态的改变,或是可预测的返回值。这种行为的设计主旨概括如下:

    1. 可以明确规定这种行为,就好像合同一样。
    2. 通过运行时检查包装上述行为,即前置条件后置条件不变项

        同任何解决方案一样,DbC也存在着它的局限。但只有知道这些局限,我们才能更好地使用它们。

            在DbC中,我们可以更多地去关注其对特定类的约束。

    检查指令

            首先需要考虑断言最简单的用法,即检查指令:当我们无法仅凭借程序执行的结果得出结论时,就可以用一个检查指令来断言我们获得的结果是否正确。其思想是在代码中表达并非显而易见的结论,这不仅可以用来测试用例,还可以作为阅读代码时的文档。

    前置条件测试

            确保客户(即调用此方法的代码)履行其合同部分。因此我们基本上需要在方法调用的最开始(准确的说,是在该方法开始执行任何操作之前)检查参数。

    后置条件

            用于检测方法的执行结果。一般放置在方法调用的末尾,return语句之前。对复杂的计算而言,后置条件是必不可少的。我们可以将那些对方法结果的约束放在后置条件中。

    不变项

            用以确保对象的状态在方法调用之间是不变的(但可以在方法执行期间偏离)。不变项只保证对象的状态信息在以下两个时间段遵守规定的规则:

    1. 进入方法前;
    2. 离开方法后。

       不变项是对对象构造后状态的保证。

            可以把不变项命名定义为一个方法,一般命名为invariant(),这个方法会在①对象构造之后以及②每个方法的开始和结束时被调用。可以这样调用该方法:

    assert invariant(); // 当禁用断言时,就不会因此产生开销了

    放宽DbC的限制

            尽管前置条件、后置条件和不变项十分有用,但在发布的产品中包含所有的DbC代码并不总是可行的。可以根据对特定位置代码的信任程度放宽DbC检查。以下是DbC检查的顺序,从最安全到最不安全:

    1. 禁用方法开头的不变项检查。
    2. 当有合理的单元测试来验证方法的返回值时,可以禁用后置条件检查。
    3. 如果确信方法体不会将对象置于无效状态(可使用白盒测试进行检测,即使用可以访问私有字段的单元测试来验证对象状态),则可以禁用方法调用结束时的不变项检查。
    4. 最不安全的,禁用前置条件检查。即使我们自己了解自己的代码,但无法控制客户传递给方法的参数

        不建议在禁用检查时直接删除执行检查的代码(只需要将其注释掉即可)。


    DbC + 单元测试

            可以将契约式设计中的概念和单元测试进行结合:

    【例子:测试一个循环队列】

            不同于以往直接编写程序,这次需要为这个队列做一些契约性的定义:

    1. 前置条件(对put():不允许将空元素添加到队列中。
    2. 前置条件(对put():将元素放入已满的队列是非法的。
    3. 前置条件(对get():尝试从空元素中获取元素是非法的。
    4. 后置条件(对get():不能从数组中获取空元素。
    5. 不变项:队列中包含对象的区域不能有任何空元素。
    6. 不变项:队列中不包含对象的区域必须只能有空值。

            ① 创建一个专用的Exception

    1. public class CircularQueueException extends RuntimeException {
    2. public CircularQueueException(String why) {
    3. super(why);
    4. }
    5. }

            ② 接下来进行CircularQueue类的创建:

    1. import java.util.Arrays;
    2. public class CircularQueue {
    3. private Object[] data;
    4. private int
    5. in = 0, // 指向下一个可用的空间
    6. out = 0; // 指向下一个出队的对象
    7. private boolean wrapped = false; // 用以判断是否回到了循环队列的开头
    8. public CircularQueue(int size) {
    9. data = new Object[size];
    10. // 构造完毕后的对象必须遵守不变项的约束
    11. assert invariant();
    12. }
    13. public boolean empty() {
    14. return !wrapped && in == out;
    15. }
    16. public boolean full() {
    17. return wrapped && in == out;
    18. }
    19. public boolean isWrapped() {
    20. return wrapped;
    21. }
    22. public void put(Object item) {
    23. precondition(item != null, "放入元素为空");
    24. precondition(!full(), "试图向已满的队列放入元素");
    25. assert invariant();
    26. data[in++] = item;
    27. if (in >= data.length) {
    28. in = 0;
    29. wrapped = true;
    30. }
    31. assert invariant();
    32. }
    33. public Object get() {
    34. precondition(!empty(), "试图从空队列中获取元素");
    35. assert invariant();
    36. Object returnval = data[out];
    37. data[out] = null;
    38. out++;
    39. if (out >= data.length) {
    40. out = 0;
    41. wrapped = false;
    42. }
    43. assert postcondition(
    44. returnval != null, "在循环队列中存在空元素");
    45. assert invariant();
    46. return returnval;
    47. }
    48. // 契约式设计的相关方法
    49. private static void
    50. precondition(boolean cond, String msg) { // 前置条件
    51. if (!cond)
    52. throw new CircularQueueException(msg);
    53. }
    54. private static boolean
    55. postcondition(boolean cond, String msg) { // 后置条件
    56. if (!cond)
    57. throw new CircularQueueException(msg);
    58. return true;
    59. }
    60. private boolean invariant() { // 不变项
    61. // 确定对象的data区域不会有空值
    62. for (int i = out; i != in; i = (i + 1) % data.length)
    63. if (data[i] == null)
    64. throw new CircularQueueException("在循环队列中存在值");
    65. // 确定对象的data区域之外只会有空值
    66. if (full())
    67. return true;
    68. for (int i = in; i != out; i = (i + 1) % data.length)
    69. if (data[i] != null)
    70. throw new CircularQueueException("在循环队列之外存在非空值:"
    71. + dump());
    72. return true;
    73. }
    74. public String dump() { // 返回更多信息
    75. return "in = " + in +
    76. ", out = " + out +
    77. ", full() = " + full() +
    78. ", empty() = " + empty() +
    79. ", CircularQueue = " + Arrays.asList(data);
    80. }
    81. }

            通常,我们会需要在代码中保留前置条件。将这些条件都封装在一个precondition()中,可以方便我们减少或关闭前置条件。注意,precondition()返回void,因为它不会和assert一起使用。

            与之相对,postcondition()invariant()都返回boolean,它们可以和assert一起使用。并且在为了性能而关闭断言时,可以直接屏蔽这些方法调用。

            ③ 创建JUnit测试

    1. import org.junit.jupiter.api.BeforeEach;
    2. import org.junit.jupiter.api.Test;
    3. import validating.CircularQueue;
    4. import validating.CircularQueueException;
    5. import static org.junit.jupiter.api.Assertions.*;
    6. class CircularQueueTest {
    7. private CircularQueue queue = new CircularQueue(10);
    8. private int i = 0;
    9. @BeforeEach
    10. public void initialize() {
    11. while (i < 5)
    12. queue.put(Integer.toString(i++));
    13. }
    14. // 辅助用方法
    15. private void showFullness() {
    16. assertTrue(queue.full());
    17. assertFalse(queue.empty());
    18. System.out.println(queue.dump());
    19. }
    20. private void showEmptioness() {
    21. assertFalse(queue.full());
    22. assertTrue(queue.empty());
    23. System.out.println(queue.dump());
    24. }
    25. @Test
    26. public void full() {
    27. System.out.println("测试:Full");
    28. System.out.println(queue.dump());
    29. System.out.println(queue.get());
    30. System.out.println(queue.get());
    31. while (!queue.full())
    32. queue.put(Integer.toString(i++));
    33. String msg = "";
    34. try {
    35. queue.put("");
    36. } catch (CircularQueueException e) {
    37. msg = e.getMessage();
    38. System.out.println(msg);
    39. }
    40. assertEquals(msg, "试图向已满的队列放入元素");
    41. showFullness();
    42. }
    43. @Test
    44. public void empty() {
    45. System.out.println("测试:Empty");
    46. while (!queue.empty())
    47. System.out.println(queue.get());
    48. String msg = "";
    49. try {
    50. queue.get();
    51. } catch (CircularQueueException e) {
    52. msg = e.getMessage();
    53. System.out.println(msg);
    54. }
    55. assertEquals(msg, "试图从空队列中获取元素");
    56. showEmptioness();
    57. }
    58. @Test
    59. public void nullPut() {
    60. System.out.println("测试:NullPut");
    61. String msg = "";
    62. try {
    63. queue.put(null);
    64. } catch (CircularQueueException e) {
    65. msg = e.getMessage();
    66. System.out.println(msg);
    67. }
    68. assertEquals(msg, "放入元素为空");
    69. }
    70. @Test
    71. public void circularity() {
    72. System.out.println("测试:Circularity");
    73. while (!queue.full())
    74. queue.put(Integer.toString(i++));
    75. showFullness();
    76. assertTrue(queue.isWrapped());
    77. while (!queue.empty())
    78. System.out.println(queue.get());
    79. showEmptioness();
    80. while (!queue.full())
    81. queue.put(Integer.toString(i++));
    82. showFullness();
    83. while (!queue.empty())
    84. System.out.println(queue.get());
    85. showEmptioness();
    86. }
    87. }

            程序执行的结果是:

            通过将DbC和单元测试向结合,我们不仅可以利用它们各自的优点,而且可以将一些DbC测试移动到单元测试之后(而不是替代它)。这样就能保证某些层次的测试。


    Guava中的前置条件

            之前提到过,前置条件是DbC中不应该删除的部分,因为它是用来检查方法参数的有效性的。因此我们最好检测它,这时Java默认的禁用断言就会造成些许麻烦,使用其他可以始终验证方法参数的库是一个不错的选择。

            这里还是使用Google的Guava库:

    【例子:使用第三方库的前置条件检测】

    1. import java.util.function.Consumer;
    2. import static com.google.common.base.Preconditions.*;
    3. public class GuavaPreconditions {
    4. static void test(Consumer c, String s) {
    5. try {
    6. System.out.println(s);
    7. c.accept(s);
    8. System.out.println("成功");
    9. } catch (Exception e) {
    10. String type = e.getClass().getSimpleName();
    11. String msg = e.getMessage();
    12. System.out.println(type + (msg == null ? "" : ": " + msg));
    13. }
    14. }
    15. public static void main(String[] args) {
    16. test(s -> s = checkNotNull(s), "X");
    17. test(s -> s = checkNotNull(s), null);
    18. test(s -> s = checkNotNull(s, "s是null"), null);
    19. test(s -> s = checkNotNull(
    20. s, "s是null,%s %s", "arg2", "arg3")
    21. , null);
    22. System.out.println();
    23. // checkArgument()会使用布尔表达式对参数进行具体的检测
    24. test(s -> checkArgument(s == "ABC"), "ABC");
    25. test(s -> checkArgument(s == "ABC"), "X");
    26. test(s -> checkArgument(s == "ABC"), null);
    27. test(s -> checkArgument(
    28. s == "ABC", "匹配失败"), null);
    29. test(s -> checkArgument(
    30. s == "ABC", "匹配失败,应该是 %s", s), null);
    31. System.out.println();
    32. // 会检测对象的状态,而不是检查参数(也可用于检查不变项)。
    33. test(s -> checkState(s.length() > 6), "长度足够长");
    34. test(s -> checkState(s.length() > 6), "不够长");
    35. test(s -> checkState(s.length() > 6), null);
    36. System.out.println();
    37. // 确保第一个参数是一个List、String或数组的有效元素索引
    38. test(s -> checkElementIndex(6, s.length()), "比6个字符长一点");
    39. test(s -> checkElementIndex(6, s.length()), "短了");
    40. test(s -> checkElementIndex(6, s.length()), null);
    41. System.out.println();
    42. // 确保其的第一个参数在0和第二个参数(包括)的范围内
    43. test(s -> checkPositionIndex(6, s.length()), "看起来和上面的差不多");
    44. test(s -> checkPositionIndex(6, s.length()), "短了");
    45. test(s -> checkPositionIndex(6, s.length()), null);
    46. }
    47. }

            程序执行的结果是:

            上述例子只演示了String类型,但Guava的前置条件是适用于所有类型的。

            另外,Guava提供的前置条件,其每个都有三种不同的重载形式:没有消息的测试、带有一个String消息的测试和带有String及替换值的可变参数列表的测试。出于效率考虑,只允许使用%s替换标签。

            因为checkNotNull()会返回参数,因此可以通过内联的方式进行使用:

    【例子:内联的checkNotNull()

    1. import static com.google.common.base.Preconditions.checkNotNull;
    2. public class NonNullConstruction {
    3. private Integer n;
    4. private String s;
    5. NonNullConstruction(Integer n, String s) {
    6. this.n = checkNotNull(n);
    7. this.s = checkNotNull(s);
    8. }
    9. public static void main(String[] args) {
    10. NonNullConstruction nnc =
    11. new NonNullConstruction(3, "ABC");
    12. }
    13. }

        编译器会判断是否进行内联。

  • 相关阅读:
    行业洞察 | 机器翻译何时能够达到可言传,可意会的境界?
    网络安全(黑客技术)—小白自学
    深入浅出理解分布式一致性Paxos算法
    VueJS面试常见的300道题(英文版)
    每日三题 6.28
    光学红外雨量IFR202型传感器智慧检测雨量场景等行业
    APK 签名 v1 v2 步骤
    知乎日报--第四周
    CSS3技巧37:JS+CSS3 制作旋转图片墙
    Ubuntu 12.04增加右键命令:在终端中打开增加打开文件
  • 原文地址:https://blog.csdn.net/w_pab/article/details/133822540