只要适用于Java,就使用“最终”修饰符[关闭]


194

在Java中,有一种做法是声明每个变量(本地或类),如果它们确实是参数,则声明final。

尽管这使代码更加冗长,但是这有助于简化代码的读取/抓取,并且由于意图被明确标记,因此还可以防止错误。

您对此有何想法,您会怎么做?


13
这可能导致宗教争论。有些人喜欢它,有些人讨厌它。我喜欢最终字段,但不喜欢最终局部变量,除非需要这样做,不确定其完全合理。不确定是否会有否决票。我同意亚历克斯·米勒的观点。;)
彼得·劳瑞

我能理解人们是否不喜欢将代码弄成final。但这是一个好的编辑者可以解决的问题:bugs.eclipse.org/bugs/show_bug.cgi?id=409379
敬请掩藏2013年

Answers:


184

我认为这都与良好的编码风格有关。当然,您可以编写出色而健壮的程序,而无需使用大量final任何地方修饰符,但是当您考虑一下时...

添加final到所有不应改变的内容加进去,只会缩小您(或下一个正在处理代码的程序员)误解或滥用导致代码生成的思维过程的可能性。至少当他们现在想要更改您以前不可变的内容时,它应该会发出一些响声。

刚开始时,final在代码中看到很多关键字看起来有点尴尬,但是很快您就会停止注意到单词本身,而只会想到,这一点永远都不会改变继续(您可以从我这里获取它;-)

我认为这是个好习惯。我并不是一直在使用它,但是只要有条件,就可以标记出final要执行的操作。


16
可以使用静态分析工具(例如FindBugs)来要求不要重新分配参数变量,而不必使用final关键字作为参数。这样,您就可以删除语法负担,并可以通过持续集成工具中的FindBugs检查来强制执行。
TimoWestkämper'5

20
@Timo这可以工作,但缺点是需要在签入后而不是在开发人员处理代码时进行检查。对于final,如果您犯了错误,编译器显然不会让您继续。
迈克(Mike)2010年

9
Eclipse有一个选项,final可以在您保存时随时添加(不变的变量)。
Bert F

22
+1我爱final。98%的时间没有影响,%1的时间给您带来的不便是小小的麻烦,但是1%的时间使我免于做一些愚蠢或意外的事情是值得的。
Bert F

14
@BertF是的,final当我“清理...”我的代码时,我总是让Eclipse在各处添加修饰符。到处都意味着即使在catch()块中,并且如前所述,过一会儿您将不会注意到它们。当然,如果我要创建一种语言,则final它将是默认语言,而a varmodifiable将是可选关键字。
Maarten Bodewes 2012年

191

痴迷于:

  • 最终字段-将字段标记为最终字段会强制它们在构造结束时进行设置,从而使该字段引用不变。这样可以安全地发布字段,并且可以避免在以后的读取中进行同步。(请注意,对于对象引用,只有字段引用是不可变的-对象引用所引用的内容仍然可以更改,并且会影响不可变性。)
  • 最终静态字段-尽管我在以前使用静态最终字段的许多情况下都使用枚举。

考虑但要谨慎使用:

  • 最终课程-框架/ API设计是我唯一考虑的情况。
  • 最终方法-与最终类基本相同。如果您使用的是疯狂的模板方法模式,并且将内容标记为final,则可能是您对继承的依赖过多,而对委托的依赖则不够。

除非感到肛门,否则忽略:

  • 方法参数和局部变量-我很少这样做,主要是因为我很懒,并且发现它会使代码混乱。我将完全承认,标记标记和我不打算修改的局部变量是“正确的”。我希望它是默认设置。但是事实并非如此,我发现遍历Final的代码更加难以理解。如果我使用别人的代码,则不会将其删除,但是如果我正在编写新代码,则不会将其放入。一个例外是必须标记一些最终内容以便可以访问它来自一个匿名内部类。

8
关于这个最后一个主题的多个问题,
尚有

4
大多数情况下,当我看到局部变量前面没有最终词并且无法在其上使用时,它告诉我可能应该在新方法中提取一些代码,该方法返回将要最终确定的期望值。当我使用某些流或其他需要尝试捕获的流时,这种情况不适用。
nyxz 2012年

32

在使用final关键字之前,您确实需要了解它的全部用法。它可以应用于变量,字段,方法和类并对其产生不同的影响

我建议您查看下面链接的文章,以了解更多详细信息。

最终关键词


29

final修改,特别是变量,是一种手段,让编译器强制执行约定,一般是明智的:确保(本地或实例)变量分配一次(不多不少)。通过确保在使用变量之前明确分配了变量,可以避免出现以下常见情况NullPointerException

final FileInputStream in;
if(test)
  in = new FileInputStream("foo.txt");
else
  System.out.println("test failed");
in.read(); // Compiler error because variable 'in' might be unassigned

通过防止多次分配变量,可以防止过分作用域。代替这个:

 String msg = null;
 for(int i = 0; i < 10; i++) {
     msg = "We are at position " + i;
     System.out.println(msg);
 }
 msg = null;

鼓励您使用此方法:

 for(int i = 0; i < 10; i++) {
     final String msg = "We are at position " + i;
     System.out.println(msg);
 }

一些链接:


1
+1为disourge泛滥的范围界定论点
汤姆·特雷桑斯基

简而言之,您基本上可以使用Javafinal使其更加基于表达式
亚当·根特2013年

实际上,“通过确保在使用变量之前已明确分配了变量,您可以避免NullPointerException的常见情况:”这是不正确的,“ final”在这里没有区别,编译器知道并抱怨“ in”变量在提供的示例中为null。
nyholku

@nyholku您是正确的,在Java中,必须在使用前明确分配局部变量,也不得使用final。但是对于领域,您需要决赛。在稍微复杂一点的示例中,“ in”是类的实例变量,而if / else在构造函数中,则需要最后一个信号来指示问题。此外,对于局部变量,最终IMO中也有一些值,因为它可以防止草率的程序员通过在声明中添加“ = null”来“修复”可能未分配的有关“ in”的编译错误。换句话说,根据我的经验,最终变量减少了对null的使用。
布鲁诺·德·弗赖恩

20

关于声明每个可能的变量,我非常教条final。其中包括方法参数,局部变量,很少包括值对象字段。我到处声明最终变量的三个主要原因:

  1. 声明意图:通过声明最后一个变量,我表示此变量只能写入一次。这对其他开发人员来说是一个微妙的暗示,对编译器来说是一个重要的暗示。
  2. 强制使用一次性变量:我相信每个变量在生活中应该只有一个目的。通过仅给每个变量一个目的,可以减少调试时达到该特定变量目的的时间。
  3. 允许优化:我知道编译器曾经有一些性能增强的技巧,这些技巧特别依赖于变量引用的不变性。我想认为其中一些旧的性能技巧(或新的技巧)将由编译器使用。

但是,我确实认为,最终类和方法几乎没有最终变量引用有用。final与这些声明一起使用时,关键字只是提供了自动测试的障碍,并以您从未料过的方式使用了代码。


2
final变量和方法参数对性能没有影响,因为它未在字节码中表示。
Steve Kuo 2014年

变量:=拉丁语:内翻>各种>可修改
Dieter

17

有效的Java的标题为“支持不可变对象”。将字段声明为final可以帮助您朝此方向迈出一些小步,但是,真正不可变的对象当然不止于此。

如果您知道对象是不可变的,则可以在许多线程/客户端之间共享它们以供读取,而无需担心同步,并且更容易推断程序的运行方式。


15
注意-最终和不变是完全不同的概念。具有final可变对象很容易-final与引用有关,可变性与对象实例有关。final Person olaf = new Person(); olaf.setName(“ Olaf”);
奥拉夫·科克

1
确切地说,不可变的对象是一回事,不可变的引用(即final)完全是另外一回事。
SCdF

2
但是它们密切相关,因为您可以通过将Person.name字段声明为final(并声明类final,然后...)使Person对象成为final。然而,不那么容易,因为只是在做“最后的人” ......
塞巴斯蒂安Ganslandt

这对于局部变量也无关紧要(参数是局部变量)。它仅适用于类变量,因此仅部分解决了该问题。
罗宾

12

我从来没有遇到过在变量上使用final关键字阻止我犯错的情况,所以目前我认为这是浪费时间。

除非有真正的理由这样做(例如,您想就该变量为最终变量提出具体观点),否则我不愿意这样做,因为我发现它会使代码的可读性降低。

但是,如果找不到它,则使代码更难阅读或编写更长的时间,那么请务必使用它。

编辑:作为一种澄清(并试图赢回不赞成票),我并不是说不要将常量标记为final,而是要不要做类似的事情:

public String doSomething() {
  final String first = someReallyComplicatedExpressionToGetTheString();
  final String second = anotherReallyComplicatedExpressionToGetAnother();

  return first+second;
}

(我认为)这只会使代码更难阅读。

还值得记住的是,所有 final操作都是防止您重新分配变量,它不会使它不变或类似的东西。


1
反驳这一说法:在很多情况下,使用final(以及通常不可变的对象)对错误的数量和影响具有重大影响。
克里斯·韦斯特

3
我全都致力于不可变对象,但我从来没有遇到过将不可变对象标记为final对我有帮助的情况。
SCdF

2
我同意我们不应该在局部变量和方法参数上使用final,因为它会使代码的可读性大大降低。
rmaruszewski

@ChrisVest,也许你的职能是太长
Pacerier

8

Final应该始终用于常量。当定义变量的规则很复杂时,它甚至对于短期变量(在一个方法中)也很有用。

例如:

final int foo;
if (a)
    foo = 1;
else if (b)
    foo = 2;
else if (c)
    foo = 3;
if (d)        // Compile error:  forgot the 'else'
    foo = 4;
else
    foo = -1;

6

听起来,反对使用final关键字的最大争论之一是“这是不必要的”,并且“浪费了空间”。

如果我们承认此处许多出色的文章所指出的“ final”的许多好处,同时承认它需要更多的键入和空间,那么我认为Java应该默认将变量设为“ final”,并要求将事物标记为“可变”。


1
下注者,在乎解释吗?
2013年

称它为变量似乎很奇怪,不是吗?
艾伦

2
有成千上万的英语单词可供选择。
2014年

太棒了。final除了“写得太久”,“使代码变得笨拙”之外,没有任何反对的论点。有几个论点final。如果您不想手动输入,我们可以在保存时自动添加。
Dmitriy Popov

5

final一直都在使用对象属性。

final当在对象属性上使用时,关键字具有可见性语义。基本上,设置最终对象属性的值是在构造函数返回之前进行的。这意味着只要您不让this引用转义构造函数,并且将其final用于所有您的属性,你的目标是(在Java 5的语义)保证的广告进行适当构造,并且因为它是不可变的也可以放心地发布到其他线程。

不可变的对象不仅与线程安全有关。它们还使您可以更轻松地推断程序中的状态转换,因为可以更改的空间是有意为之的,并且如果始终使用,则完全限于仅更改的内容。

有时我还会使方法定型,但不那么频繁。我很少把课程定下来。我通常这样做是因为我几乎不需要。我通常不太多使用继承。我更喜欢使用接口和对象组合-这也使它适合于我发现通常更易于测试的设计。当您编写接口而不是具体类的代码时,则不需要在测试时使用继承,就象使用jMock这样的框架一样,使用接口创建模拟对象比使用具体类要容易得多。

我想我应该把大部分课程都定下来,但是我还没有习惯。


4

我必须阅读很多工作代码。缺少实例变量上的final是让我烦恼的最重要的事情之一,这使理解代码变得不必要地困难。为了我的钱,关于局部变量的最终结果会导致混乱而不是清晰。语言的设计应使其成为默认语言,但我们必须忍受错误。有时,这对于使用if-else树进行循环和确定赋值特别有用,但是大多数情况下,它往往表明您的方法过于复杂。


为什么不使用减少混乱的工具?
Pacerier

@Pacerier就像在编辑器中错误表示代码一样?
汤姆·霍顿

3

显然,final应该用于常量并强制不变性,但是方法还有另一个重要用途。

有效的Java对此有一个整体(第15项),指出了意外继承的陷阱。实际上,如果您没有为继承设计和编写类,则从该类继承会带来意想不到的问题(该项目提供了一个很好的例子)。因此,建议您在不希望从其继承的任何类和/或方法上使用final

这可能看起来很苛刻,但这是有道理的。如果要编写供其他人使用的类库,那么您不希望它们从非为它设计的东西中继承-您将自己锁定在该类的特定实现中以实现向后兼容。如果您在团队中进行编码,则没有什么可以阻止团队中的另一个成员(如果确实需要)删除决赛。但是关键字使他们思考自己在做什么,并警告他们继承的类不是为此而设计的,因此应格外小心。


3

另一个警告是,许多人将final弄混了,这意味着实例变量的内容不能更改,而不是引用不能更改。


这篇文章就是一个很好的证明。
inigoD

2

即使对于局部变量,知道它已声明为final也意味着我不必担心以后会更改引用。这意味着在调试时,稍后再看到该变量时,我确信它引用的是同一对象。寻找错误时,我不必担心这件事。一个额外的好处是,如果将99%的变量声明为final,那么实际上是变量的少数变量会更好。此外,最终结果使编译器可以发现更多可能愚蠢的错误,否则这些错误可能不会引起注意。


2

final为每种方法中的每个参数选择类型都会给编码人员和代码读取人员造成很大的困扰。

一旦刺激超出合理范围,请切换到Scala,默认情况下参数为final。

或者,您始终可以使用代码样式工具来自动为您执行此操作。所有IDE都将它们实现或作为插件来实现。


1

Final与Java中的变量一起使用时,可以替代C ++中的常量。因此,当final和static用于变量时,它变得不可变。同时使迁移的C ++程序员很高兴;-)

与引用变量一起使用时,尽管可以操纵该对象,但它不允许您重新引用该对象。

当final与方法一起使用时,它不允许子类重写该方法。

一旦用法非常明确,应谨慎使用。它主要取决于设计,因为在该方法上使用final不利于多态。

当您确定该变量的值将/永远不会更改时,应仅将其用于变量。还要确保遵循SUN鼓励的编码约定。例如:final int COLOR_RED = 1; (大写字母由下划线分隔)

对于引用变量,仅当我们需要对特定对象的不变引用时才使用它。

关于可读性部分,请确保在使用final修饰符时注释起着非常重要的作用。


有人可以告诉我为什么这是投反对票的吗???只是好奇..
全能的

可能是因为您声明只应在XYZ时将其定型。其他人则认为最好的做法是使一切都最终化,除非需要这样做。
Outlaw程序员

1
它根本不能替代C ++ const关键字。-1。
tmj

1

我从未在局部变量上使用它们,添加的冗长性毫无意义。即使您不认为应该重新分配该变量,也不会对下一个更改该代码的人产生太大的影响,否则代码将以其他方式更改,并且由于代码已被更改,因此使其最终定稿的任何原始目的可能不再有效。如果只是为了清楚起见,我相信它会由于冗长的负面影响而失败。

成员变量几乎也一样,因为除了常量的情况外,它们几乎没有好处。

它也与不变性无关,因为关于不变性的最好指示就是它是这样记录的,并且/或者没有可以改变对象的方法(这与使类为final是唯一的保证它是不可变的)。

嘿,那只是我的意见:-)


1

我将Eclipse设置为在所有未修改的字段和属性上添加final。使用Eclipse的“保存动作”,在保存文件时添加了这些最终修饰符(以及其他功能),效果很好。

强烈推荐。

查看关于Eclipse Save Actions的博客文章


1

对于论点,我认为不需要它们。Mostley他们只是在伤害阅读能力。重新分配一个自变量非常愚蠢,以至于我应该非常确信它们无论如何都可以当作常量来对待。

Eclipse将最终的颜色涂成红色的事实使我更容易发现代码中的变量声明,我认为这在大多数情况下都提高了可读性。

我试图强制执行所有变量都应为最终变量的规则,这是没有极端理由的。回答“这个变量是什么?”要容易得多。问题,您是否只需要找到初始化并有信心就可以了。

实际上,现在几天来,我对非最终变量感到非常紧张。这就像是把刀悬挂在头顶的线中,还是只是把它放在厨房的抽屉里一样。

最终变量只是获取值的一种好方法。

非最终变量绑定到某些易错算法的一部分。

一个不错的功能是,在大多数情况下,当选择在算法中不使用变量时,解决方法是改写一种方法,这通常可以显着改善代码。


1

我已经编码了一段时间,并在可能的情况下使用final。经过一段时间(对于变量,方法参数和类属性),我可以说我的变量中有90%(或更多)实际上是最终变量。我认为在您不想修改变量时的好处(我以前见过,有时很痛苦)会为代码中的额外键入和额外的“最终”关键字带来好处。

话虽这么说,如果我要设计一种语言,除非每个其他关键字都没有修改,否则我将使每个变量都成为最终变量。

我认为,对于类和方法,我很少使用final。这是一个或多或少复杂的设计选择,除非您的类是一个实用程序类(在这种情况下,您应该只有一个私有构造函数)。

在需要时,我还使用Collections.unmodifiable ...创建无法修改的列表。


0

对事件侦听器使用匿名本地类,这是Java中的一种常见模式。final关键字最常见的用法是确保范围内的变量可供偶数侦听器访问。

但是,如果您发现自己需要在代码中放入很多最终语句。这可能是您做错了一个很好的提示。

上面发布的文章提供了以下示例:

public void doSomething(int i, int j) {
    final int n = i + j; // must be declared final

    Comparator comp = new Comparator() {
        public int compare(Object left, Object right) {
            return n; // return copy of a local variable
        }
    };
}

0

我将其用于内部和外部方法中的常量。

我有时仅将其用于方法,因为我不知道子类是否不想覆盖给定的方法(无论出于何种原因)。

至于类,仅对于某些基础结构类,我使用了最终类。

如果将函数参数写入函数内部,则IntelliJ IDEA会警告您。因此,我已经停止将final用于函数参数。我在Java Runtime库中也看不到它们。


0

我几乎不使用final方法或类,因为我喜欢允许人们覆盖它们。

否则,只有在 public/private static final type SOME_CONSTANT;


嗯..编辑在一个地方..仍然是最后说在第二行;-)
全能的

允许人们覆盖值是令人惊讶和难以理解的错误的唯一最大来源
RAY

0

将类标记为final也可以使某些方法绑定在编译时而不是在运行时发生。考虑下面的“ v2.foo()”-编译器知道B不能具有子类,因此foo()不能被覆盖,因此在编译时就知道要调用的实现。如果未将类B标记为final,则v2的实际类型可能是某个扩展B并覆盖foo()的类。

class A {
    void foo() {
        //do something
    }
}
final class B extends A {
    void foo() {
    }
}
class Test {
    public void t(A v1, B v2) {
        v1.foo();
        v2.foo();
    }
}

-1

强烈建议将final 用于常量。但是,我不会将它用于方法或类(或至少要考虑一会儿),因为它会使测试更加困难,甚至不是不可能。如果绝对必须使类或方法最终定型,请确保该类实现某些接口,以便您可以模拟实现相同的接口。


它不会使测试更加困难,因为无论如何您都应该使用接口。
克里斯·韦斯特
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.