【问题标题】:Should I pre-initialize a variable that is overwritten in multiple branches?我应该预初始化在多个分支中被覆盖的变量吗?
【发布时间】:2019-09-13 20:17:44
【问题描述】:

有一个方法:

private String myMethod(String gender)
{
    String newString = "";
    if(gender.equals("a"))
        newString = internal.getValue();
    else
        newString = external.getValue();

    return newString;
}

我重构了所有内容,但做了一个小改动: String newString; 而不是:String newString = "";

此重构是否改进了代码?我知道Stringnull,当我们不初始化它时,但在这个例子中,它的值总是来自ifelse。这次重构有什么改变吗?

【问题讨论】:

  • 在这里返回两次将是更具可读性的解决方案。顺便说一句,您的代码示例缺少返回。此外,初始分配令人困惑,因为它的值从未被使用并且总是被覆盖。这是关于意图的清晰性,初始分配使整体代码更加不清楚
  • 在两个分支中你都给它一个值,所以在前一行给它一个“初始”值是没有意义的。您可以用String newString = gender.equals("a") ? internal.getValue() : external.getValue(); 替换整个内容
  • 未初始化的local变量not为空;它们根本没有可观察到的价值。
  • 另请注意,Java 7 及更高版本允许在 switch 语句中使用字符串。对于比这更复杂的代码,这可能非常有用。

标签: java string


【解决方案1】:

回答直接问题:这里最初不需要赋值;代码执行的所有分支都将为newString 赋值。因此,您根本不需要初始化它。否则,我将初始化为您想要的任何“默认”值。

我只返回一个三元组,而不是两个返回或一个分支语句来分配一个变量:

private String myMethod(String gender) {
    return gender.equals("a")
            ? internal.getValue()
            : external.getValue();
}

【讨论】:

  • 值得一提的是,条件运算符并不是if/else的语义替代。这在这里并不重要,但是,比如说,Number foo(boolean c) { if (c) return Integer.valueOf(0); else return Double.valueOf(0); }return c ? Integer.valueOf(0) : Double.valueOf(0); 不同,因为后者总是导致 Double
  • 是的,但在这种情况下它们是Strings,我真的不建议返回这样一个具有区分 double 与 int 行为的抽象类型。事实上,通过使用Number,您将自己限制在#intValue etc/casting 中,然后才能以有意义的方式使用该数字(没有数学运算符)。三元将键入最具体的超类型绑定,这是相关的,但我只是认为这里具体的数字示例无论如何都是一个坏主意。
  • 你可能不建议这样做,但我可以向你保证它会发生(我写了the check banning it in Google's codebase,它也在 JDK 中找到了一个实例)。
  • 哦,当然,人们会这样做。我的意思是,尝试围绕某些方法使用双 整数返回(即使是功能性的)进行设计似乎是一个严重的缺陷。此外,容易出错的粉丝。
【解决方案2】:

初始化String好还是留空好?

你的前提是有缺陷的:不初始化字符串并不意味着它的值为空。

在分配之前,您不得使用局部变量,以免您不小心使用了您不希望的值。因此,该值不是“null”,而是未定义 (*)。

这称为definite assignment checking,用于防止某些类型的错误。如果你给变量一个你不需要给它的值,你就禁用了这个检查,这样就会对编译器试图保护你的错误开放。

例如,如果代码如下所示:

private String myMethod(String gender)
{
    String newString = "";
    if(gender.equals("a"))
        newString = internal.getValue();
    else if (gender.equals("b");
        newString = external.getValue();
    // Oops! meant to check if gender.equals("c")

    return newString;
}

你可能有一个错误,因为有一个你没有检查过的缺失案例。

如果您已将null 显式分配给变量,您将遇到同样的问题;但是现在您的方法将返回 null,因此可能会在调用代码中导致 NPE。

如果您省略了= "",编译器将阻止您在返回中使用newString

(分配和重新分配变量也意味着该变量不会是有效的最终变量,因此您将无法在 lambda 或匿名类中使用它)。


(*) 这仅适用于局部变量和final 成员/静态变量。如果类成员不是最终的,则不必在使用前明确分配,这是一个丰富的错误接缝,也是尽可能使类成员成为最终的一个很好的理由。而且,从技术上讲,final 成员首先被初始化为其类型的默认值,因此您实际上可以在初始化之前将它们读取为 null

【讨论】:

  • +1 我认为这个答案最好地回答了 OP 提出的问题 Does this refactor improve the code?,因为它讨论了 definite assignment checking
【解决方案3】:

如果有使用初始值的场景,最好只初始化String(或其他任何东西)。

在您的情况下,您已将 newString 分配给一个字符串文字,该文字没有任何用途,只会让读者感到困惑。

很明显,性能和功能不会以任何相关的方式改变。

【讨论】:

    【解决方案4】:

    我对没有三元运算符的最短形式的看法(我认为这会降低可读性):

    private String myMethod(String gender)
    {
        if(gender.equals("a"))
            return internal.getValue();
        return external.getValue();
    }
    

    我可能会有一个完整的 if {...} else {...} 构造,就像我自己的代码中的其他答案一样。

    此外,并非所有调试器都可以轻松地显示作为正常流程的一部分从方法返回的内容,因此如果在变量中捕获返回值然后返回(断点可以放在返回声明)

    【讨论】:

    • 你认为"a".equals(gender)更好,因为它可以处理null
    • @KorayTugay 仅当您期望 gender 可以为空时。否则,这只会​​隐藏导致gender 意外为空的错误。
    • @KorayTugay 这样做会巧妙地改变代码的行为,因此它不是适当的重构。这可能是一些最危险的偷渡“修复”,因为可能存在依赖于这种行为的调用代码(但希望不是),然后你会巧妙地破坏它。处理这个问题的最好方法是在 javadoc 中记录 null 是预期的(甚至可能使用 null-is-allowed annotation 进行注释)或引入一个明确的检查gender 不为 null。 (Options.requireNotNull 非常适合这个)
    【解决方案5】:

    您可以将此字符串设为final 并保持未分配,以确保所有if 分支都分配该值:

    final String result;
    if (condition1) {
        result = "one";
    } else if (condition2) {
        result = "two";
    } else {
        result = "other";
    }
    return result;
    

    使用这种方法,编译器将检查result 变量是否在每个分支中分配了一次。如果您再添加一个条件分支,或者您尝试错误地覆盖变量,这可能会有所帮助 - 编译器将失败并显示错误。

    【讨论】:

      【解决方案6】:

      在您的情况下(如果其他情况),无需初始化String,您可以简单地将其设置为String newString;,这样就可以了,因为无论哪种方式,最后它都会有不同的值。

      private String myMethod(String gender)
      {
          String newString;
      
          if(gender.equals("a"))
              newString = internal.getValue();
          else
              newString = external.getValue();
      
          // Missing return statement.
      }
      

      另外,我看到您有一个返回字符串的函数。假设您将返回 newString 变量,而不是创建字符串变量,您可以简单地返回条件中的字符串:

      private String myMethod(String gender)
      {
          if(gender.equals("a"))
              return internal.getValue();
          else
              return external.getValue();
      }
      

      【讨论】:

        【解决方案7】:

        我的大学是对的,这可以通过三元运算符来完成。 此外,我认为尽可能多地防止 NullPoiterExeptions 非常重要。 如果性别为空怎么办?空指针异常 我会像这样切换“a”和性别:

            private String myMethod(String gender) {
                return "a".equals(gender)
                    ? internal.getValue()
                    : external.getValue();
            }
        

        【讨论】:

          【解决方案8】:

          根据Java doc

          默认值

          声明字段时并不总是需要赋值。已声明但未初始化的字段将由编译器设置为合理的默认值。一般来说,此默认值将为零或空,具体取决于数据类型。然而,依赖这样的默认值通常被认为是糟糕的编程风格。

          局部变量略有不同;编译器永远不会为未初始化的局部变量分配默认值。如果您无法在声明它的地方初始化局部变量,请确保在尝试使用它之前为其分配一个值。访问未初始化的局部变量将导致编译时错误。

          【讨论】:

            猜你喜欢
            • 2016-03-24
            • 2020-03-18
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2017-07-18
            • 1970-01-01
            相关资源
            最近更新 更多