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


105

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

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

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


@Amir编码风格的问题似乎比SO更好,而且我在FAQ或此网站的meta中找不到任何与此相关的政策。你能指导我吗?
橡树

1
@Oak它说:“特定的编程问题,软件算法,编码,请向堆栈溢出询问”
Amir Rezaei

7
@Amir我不同意,我看不出这是一个编码问题。无论如何,这场辩论不属于这里,因此我就此问题开了一个元主题
橡树

3
@amir这完全是本网站的主题,因为它是有关编程的主观问题-请参阅/ faq
Jeff Atwood

1
关于局部变量:较早的Java编译器会注意是否声明局部变量final,并进行了相应的优化。现代编译器足够聪明,可以自行解决。至少在局部变量上,final完全是为了人类读者的利益。如果您的例程不太复杂,那么大多数人类读者也应该能够自己弄清楚。
所罗门慢速

Answers:


67

我使用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

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


1
关于编辑-我知道了的语义final,谢谢:)但简短而干净的方法的好处-我猜想,如果该方法足够短,很明显没有将变量重新分配给它,考虑final关键字的动机更少。
橡树

如果我们可以有最终参数和局部变量,而且语法又简短又干净,那不是很好吗?programmers.stackexchange.com/questions/199783/...
oberlies

7
“最终不能保证您不能更改(非原始)变量的值/状态。只有您不能在初始化后将引用重新分配给该对象。” 非原始=引用(Java中唯一的类型是原始类型和引用类型)。引用类型的变量的值引用。因此,您不能重新分配引用=您不能更改值。
user102008 2015年

2
+1为参考/状态陷阱。请注意,尽管标记变量final对于在Java8的新功能方面创建闭包可能是必要的
Newtopian,2016年

@ user102008指出您的比较有偏见。变量赋值与其值更新不同
ericn

64

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

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

这就是为什么您应该使用final关键字的原因。幽州知道它永远不会被重新分配,但没有人知道。final立即使用消除了您的代码歧义。


8
如果您的方法很明确并且只做一件事,那么读者也会知道。最终字只会使代码不可读。如果不可读
那就

4
@eddieferetro不赞成。关键字final表示意图,这使代码更具可读性。另外,一旦必须处理现实世界的代码,您会发现它很少是原始且清晰的,并且final在任何地方都可以自由添加s,这将有助于您发现错误并更好地理解旧代码。
Andres F.

4
变量永远不会更改,并且代码依赖于这一事实是“意图” 吗?还是目的是“恰好发生此变量永不更改,所以我将其标记为最终值”。后者是无用的并可能有害的噪音。而且,由于您似乎主张标记所有本地人,因此您要稍后做。大-1。
user949300 '16

2
final声明意图,这通常在一段代码中是一件好事,但这是以增加视觉混乱为代价的。像编程中的大多数事情一样,这里有一个折衷:表示您的变量仅会被使用一次,这一事实值得增加冗长吗?
christopheml

30

尽可能使用final/的优点之一const是,它减少了代码阅读者的心理负担。

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

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


16
好吧,在Java final中,不能保证您不能更改(非基本)变量的值/状态。只是您不能在初始化后将引用重新分配给该对象。
彼得Török

9
我知道,这就是为什么我区分价值和参考。在不变的数据结构和/或纯功能的情况下,该概念最有用。
LennyProgrammers

7
@PéterTörök它对原始类型立即有帮助,对可变对象的引用也有帮助(至少您知道您始终在处理同一个对象!)。在处理从头到尾都是不变的代码时,它非常有用。
Andres F.

18

我认为final方法参数和局部变量是代码噪声。Java方法声明可能会很长(尤其是泛型),因此无需再进行声明。

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

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

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

除了在参数中添加额外的关键字(从而使恕我直言伪装)的明显效果之外,将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,它不应该也不可以防止,因为您可以请将其排除在范围之外(甚至分配nullinput那是否仍然有帮助)。

使用“最终”选项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
}

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

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

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


还要注意,final实际上并没有像乍看起来那样真正地保护您:

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

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


在最后一种情况下,final确实部分保证了您正在处理同一Date实例。有些保证总比没有好(如果有的话,您在争辩不可变的类!)。无论如何,在实践中,您所说的大部分内容应该会影响现有的默认不可变语言,但不会如此,这意味着这不是问题。
Andres F.

+1表示“代码可能会使用输入”的风险。不过,我还是希望我的参数为final,对于上述情况,有可能使它们成为非最终参数。只是不值得用关键字来散布签名。
maaartinus

7

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


2
另外,对于参数,如果分配了参数,则可以让编译器发出警告或错误。
2013年

3
相反,我发现很难阅读,因为它会使代码混乱。
史蒂夫郭

3
@Steve Kuo:它只允许我快速找到所有变量。没有什么大的收获,但是与防止意外分配一起,这值得6个字符。如果有类似var标记非最终变量之类的东西,我会非常高兴。YMMV。
maaartinus

1
@maaartinus同意var!不幸的是,这不是Java中的默认设置,现在更改语言为时已晚。因此,我愿意忍受写作上的不便final:)
Andres F.

1
@maaartinus同意。我发现初始化后大约80-90%的(局部)变量不需要修改。因此,其中80-90%的用户将final关键字放在前面,而只有10-20%的用户需要使用关键字var...
JimmyB,2016年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.