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

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

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

评论

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

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

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

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

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

#1 楼

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


这意味着在许多情况下,它仍然不能使理解代码内部发生的事情变得更加容易,而误解实际上可能会导致一些细微的错误,很难检测到。

评论


关于编辑-我知道final的语义,谢谢:),但关于简短方法的好处-我猜想,如果该方法足够简短,很明显不会重新分配变量,考虑使用final关键字的动机就更少了。

–橡木
2011-02-16 10:35



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

– oberlies
13年5月29日在13:05

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

–user102008
2015年12月5日,0:12

+1为参考/状态陷阱。请注意,尽管在Java8的新功能方面创建了闭包可能需要标记变量final

– Newtopian
16年8月26日在14:22

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

–ericn
18-10-5在4:11

#2 楼

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


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


这就是为什么您应该使用final关键字的原因。您声明自己知道永远不会将其重新分配,但是没有人知道。使用final立即消除您的代码歧义。

评论


如果您的方法很明确并且只做一件事,那么读者也会知道。最后的单词只会使代码不可读。如果不可读,那就更加含糊了

– eddieferetro
16-3-3在14:46

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

– Andres F.
16年8月26日在2:21

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

–user949300
16-8-26在4:19



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

–christopheml
17年10月11日在8:30

使用语法突出显示时,视觉混乱将大大减少。我用final标记只分配一次的本地人,并且只想分配一次。那可能是大多数当地人,但显然不是全部。对我而言,没有“永不改变的可能性”。在我的世界中,有两种明显分离的当地人。那些根本不应该再次更改(最终)的变量,以及那些必须根据当前任务所指示的任务进行更改的变量(这些变量很少,使函数易于推论)。

–blubberdiblub
19-10-23在14:50

#3 楼

尽可能使用final / const的优点之一是可以减少代码阅读者的精神负担。
确保读者以后不会更改值/引用。因此,开发人员无需为了理解计算而进行修改。
学习纯函数式编程语言后,我已经改变了主意。知道您可以相信“变量”始终保持其初始值,这真是一种欣慰。

评论


好吧,在Java final中不能保证您不能更改(非基本)变量的值/状态。只有这样,您才能在初始化后将引用重新分配给该对象。

–彼得·托克(PéterTörök)
2011-2-16在9:35

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

– LennyProgrammers
2011-02-16 11:07



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

– Andres F.
16年8月26日在2:23

#4 楼

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

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

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

当然,如果需要将它们定型,例如,因为您正在使用匿名类中的值,那么没问题-这是最简单的最简单的解决方案。除了在参数中添加额外的关键字并由此恕我直言伪装的明显效果外,将final添加到方法参数通常会使方法主体中的代码变得更不易读,这会使代码变得更糟-要变得“好”,代码必须具有尽可能高的可读性和简单性。举一个人为的例子,假设我有一个方法需要不区分大小写。

没有final

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


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

现在带有'final',选项1:

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


在设置参数final时,编码器将停止添加代码如果不再认为他使用的是原始值,则存在同样的风险,即更深一层的代码可能使用input而不是lowercaseInput,它不应也不能受到保护,因为您不能超出范围(甚至将null分配给input(如果仍然有帮助的话)。

使用'final',选项2:

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


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

使用'final',选项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.
16年8月26日在2:29

+1表示“代码可能会使用输入”的风险。尽管如此,我还是希望我的参数是最终参数,对于上述情况,可以将其设为非最终参数。只是不值得用关键字来散布签名。

– maaartinus
16年8月26日在4:50

我发现有人可能会错误地使用输入而不是小写输入模拟。尽管从技术上讲是正确的,但一旦导致错误,就很容易发现错误,因为您可以在使用时清楚地看到2个单独命名的变量的不同语义(前提是您给它们起了好名字)。对我来说,将两个值在语义上不同的用法混和到相同的变量中并因此将其命名为更危险。由于您必须阅读和理解与1变量某种程度上相关的所有先前代码,因此使查找错误更加困难。

–blubberdiblub
19-10-23在15:05



在这里,功能强大的装饰者将赢得胜利。 withLowerCased(input,input-> {...方法主体}); public T withLowerCased(String input,Function ){return f.apply(input.toLowerCase()); }。现在我们可以遵守不变性,不必添加异常路径(我认为这会破坏SOLID),并且避免进行局部重新分配。

– PlexQ
20年7月5日在16:46



#5 楼

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

评论


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

– oberlies
13年5月29日在13:03

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

–郭富城
13年2月2日于15:32

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

– maaartinus
13年11月12日在11:26

@maaartinus同意var!不幸的是,它不是Java中的默认值,现在更改语言为时已晚。因此,我愿意忍受final编写带来的不便:)

– Andres F.
16年8月26日在2:24

@maaartinus同意。我发现初始化后大约80-90%的(局部)变量不需要修改。因此,其中80-90%的用户将final关键字放在前面,而只有10-20%的用户需要使用变数...

– JimmyB
16-10-24在11:54