it-swarm.cn

在Java中,即使不必使用“ final”作为参数和局部变量,我也应该使用吗?

Java允许将变量(字段/本地变量/参数)标记为final,以防止重新分配到变量中。我发现它对字段非常有用,因为它可以帮助我快速查看某些属性(或整个类)是否是不变的。

另一方面,我发现它对局部变量和参数的用处不大,通常我避免将它们标记为final,即使它们永远不会被重新分配(明显的例外,当它们需要重新分配时)在内部类中使用)。但是,最近,我遇到了最终可用的代码,我想从技术上讲,它可以提供更多信息。

不再对我的编程风格充满信心,我想知道在任何地方应用final还有什么优点和缺点,什么是最常见的行业风格,以及为什么。

114
Oak

我使用final的方式与您相同。对我来说,它在局部变量和方法参数上看起来是多余的,并且没有传达有用的额外信息。

重要的是要努力使我的方法简短而干净,每个都做一个任务。因此,我的局部变量和参数的范围非常有限,并且仅用于单个目的。这样可以最大程度地减少无意中重新分配它们的机会。

此外,您肯定知道final不保证您不能更改(非基本)变量的值/状态。只是您不能在初始化后将引用重新分配给该对象。换句话说,它仅与原始或不可变类型的变量无缝地协同工作。考虑

final String s = "forever";
final int i = 1;
final Map<String, Integer> m = new HashMap<String, Integer>();

s = "never"; // compilation error!
i++; // compilation error!
m.put(s, i); // fine

这意味着,在许多情况下,它仍然不能使理解代码内部发生的事情变得更加容易,并且误解了它实际上可能会导致难以发现的细微错误。

68
Péter Török

您的Java编程风格和想法很好-不必在那里怀疑自己。

另一方面,我发现它对局部变量和参数的用处不大,通常我避免将它们标记为final,即使它们永远不会被重新分配(明显的例外是当它们需要在内部类中使用时) )。

这就是为什么您应该使用final关键字的原因。您声明[〜#〜] you [〜#〜]知道它永远不会被重新分配,但是没有人知道。立即使用final可以消除代码歧义。

66
J.K.

尽可能使用final/const的一个好处是,它减轻了代码阅读者的负担。

他可以放心,以后不会更改任何值/参考。因此,他不需要为了理解计算而注意修改。

在学习了纯函数式编程语言之后,我已经改变了主意。男孩,如果您可以信任“变量”以始终保持其初始值,那真是一种解脱。

32
LennyProgrammers

我认为方法参数和局部变量中的final是代码噪声。 Java方法声明可能会很长(特别是对于泛型而言)-无需再进行声明。

如果正确编写了单元测试,则将分配给“有害”的参数,因此它永远不会实际上成为问题。视觉清晰度比避免可能由于单元测试的覆盖范围不足而未被发现的错误更重要。

如果您对参数或局部变量进行了分配,则可以将诸如FindBugs和CheckStyle之类的工具配置为破坏构建,如果您非常在意这些事情的话。

当然,如果您need使它们最终化,例如,因为您使用的是匿名类中的值,那么就没问题-这是最简单的最简单的解决方案。

除了在参数中添加额外的关键字(从而使恕我直言伪装)的明显效果之外,在方法参数中添加final常常会使方法主体中的代码变得不易读,这会使代码变得更糟-成为“好”代码必须可读性和尽可能简单。举一个人为的例子,假设我有一个方法需要不区分大小写地工作。

没有final

_public void doSomething(String input) {
    input = input.toLowerCase();
    // do a few things with input
}
_

简单。清洁。每个人都知道发生了什么事。

现在使用“最终”选项1:

_public void doSomething(final String input) {
    final String lowercaseInput = input.toLowerCase();
    // do a few things with lowercaseInput
}
_

虽然使参数final阻止编码器继续往下添加代码,以免他认为自己正在使用原始值,但同样的风险是,往后编码可能会使用input而不是lowercaseInput ,它不应该也不可以防止,因为您不能超出范围(或者甚至可以将null分配给input)。

使用“最终”选项2:

_public void doSomething(final String input) {
    // do a few things with input.toLowerCase()
}
_

现在,我们刚刚产生了更多的代码噪音,并引入了必须调用toLowerCase() n次的性能问题。

使用“最终”选项3:

_public void doSomething(final String input) {
    doSomethingPrivate(input.toLowerCase());
}

/** @throws IllegalArgumentException if input not all lower case */
private void doSomethingPrivate(final String input) {
    if (!input.equals(input.toLowerCase())) {
        throw new IllegalArgumentException("input not lowercase");
    }
    // do a few things with input
}
_

谈论代码噪声。这是火车残骸。我们有了一个新方法,一个必需的异常块,因为其他代码可能会错误地调用它。更多的单元测试将涵盖该异常。所有这些都避免了一条简单且恕我直言的可取且无害的路线。

还有一个问题是方法的时间不能太长,以至于您不能轻易地以视觉方式接受它,并且一眼就知道已经对参数进行了赋值。

我确实认为,如果您为参数分配一个参数,则应在方法的每个早期阶段(最好是在基本输入检查之后的第一行或直线处)都进行有效地替换entire方法,该方法在方法内具有一致的效果。读者知道,期望任何分配都是显而易见的(在签名声明附近)并且在一致的位置,这大大减轻了添加final试图避免的问题。实际上,我很少分配参数,但是如果这样做,我总是在方法的顶部进行。


还要注意,final并没有像最初看起来那样保护您:

_public void foo(final Date date) {
    date.setTime(0); 
    // code that uses date
}
_

除非参数类型是原始的或不可变的,否则final不会完全保护您。

22
Bohemian

我让Eclipse在每个局部变量之前放置final,因为我认为它使程序更易于阅读。我不使用参数,因为我想使参数列表尽可能短,理想情况下,它应该放在一行中。

7
maaartinus