在Java中适用时使用“final”修饰符

时间:2008-09-26 05:05:59

标签: java oop

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

虽然这使得代码更加冗长,但这有助于轻松阅读/掌握代码,并且还可以防止错误,因为意图已明确标记。

您对此有何看法?您对此有何看法?

25 个答案:

答案 0 :(得分:180)

痴迷:

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

考虑但明智地使用:

  • 最终类 - 框架/ API设计是我认为的唯一情况。
  • 最终方法 - 与最终类基本相同。如果你正在使用疯狂的模板方法模式和标记最终的东西,你可能过分依赖继承而不是委托。

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

  • 方法参数和局部变量 - 我很可能这样做很大程度上是因为我很懒,我发现它使代码混乱。我将完全承认,我不打算修改的标记参数和局部变量是“更严格”。我希望这是默认的。但事实并非如此,而且我发现整个决赛的代码更难以理解。如果我在别人的代码中,我不会把它们拉出去,但是如果我正在编写新的代码,我就不会把它们放进去。一个例外是你需要标记最终的东西以便你可以访问它来自一个匿名的内部类。

答案 1 :(得分:172)

我认为这一切都与良好的编码风格有关。当然,你可以编写好的,健壮的程序而不需要在任何地方使用很多final修饰符,但是当你想到它时......

final添加到不应该更改的所有内容中,只会缩小您(或下一个程序员,处理您的代码)会误解或误用导致的思维过程的可能性在你的代码中。至少它应该响起一些钟声,因为他们现在想要改变你以前不变的东西。

首先,在您的代码中看到很多final个关键字看起来很尴尬,但很快你就会停止注意到这个词本身并且只会想到, that-thing-will - 从这一点开始改变(你可以从我这里拿走它; - )

我认为这是一种很好的做法。我并没有一直使用它,但是当我可以并且标记某些内容final时我会这样做。

答案 2 :(得分:32)

在使用之前,您确实需要了解final关键字的完整用法。它可以应用于变量,字段,方法和类,并且具有不同的影响

我建议查看下面链接的文章了解更多详情。

Final Word On the final Keyword

答案 3 :(得分: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);
 }

一些链接:

答案 4 :(得分:20)

关于声明每个可能的变量final,我非常教条。这包括方法参数,局部变量和很少的值对象字段。我有三个主要原因可以在任何地方声明最终变量:

  1. 声明意图:通过声明一个最终变量,我声明这个变量只能写入一次。这是对其他开发人员的一个微妙暗示,也是编译器的一个重要提示。
  2. 强制执行一次性变量:我相信每个变量在生活中应该只有一个目的。通过给每个变量只有一个目的,您可以减少在调试时理解该特定变量的目的所需的时间。
  3. 允许优化:我知道编译器曾经有过性能增强技巧,它特别依赖于变量引用的不变性。我喜欢认为编译器会使用这些旧的性能技巧(或新的)。
  4. 但是,我认为最终的类和方法并不像最终变量引用那样有用。与这些声明一起使用时,final关键字只是为自动化测试提供了障碍,并以您从未预料到的方式使用您的代码。

答案 5 :(得分:17)

Effective Java有一个项目“赞成不可变对象”。将字段声明为final可以帮助您朝着这个方向迈出一些小步骤,但对于真正不可变的对象来说当然还有更多。

如果你知道对象是不可变的,那么它们可以被共享以便在没有同步担忧的情况下在许多线程/客户端之间进行读取,并且更容易推断出程序的运行方式。

答案 6 :(得分:10)

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

除非有这样做的真正原因(因为你想要对该变量做出最终的具体观点)我宁愿不这样做,因为我发现它使代码的可读性降低。

但是,如果你没有发现它使代码更难以阅读或更长时间写,那么一定要去。

编辑:作为澄清(并尝试赢回选票),我并不是说不要将常数标记为最终,我说不要做类似的事情:

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

  return first+second;
}

只是让代码(在我看来)更难阅读。

还值得记住的是,所有最终确实会阻止您重新分配变量,它不会使其变为不可变或类似的东西。

答案 7 :(得分: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;

答案 8 :(得分:5)

使用final关键字反对的最大争论之一就是“它不必要”,而且“浪费空间”。

如果我们承认这里有许多伟大帖子所指出的“最终”的诸多好处,虽然承认它需要更多的打字和空间,但我认为Java应该默认变量为“final”,并要求事情如果编码员希望它被标记为“可变”。

答案 9 :(得分:5)

我一直使用final作为对象属性。

final关键字在对象属性上使用时具有可见性语义。基本上,在构造函数返回之前设置最终对象属性的值。这意味着只要你不让this引用转义构造函数并且你使用final 所有属性,你的对象就是(在Java 5语义下)保证正确构造,并且由于它也是不可变的,因此可以安全地发布到其他线程。

不可变对象不仅仅是关于线程安全的。它们还可以更容易地推断出程序中的状态转换,因为可以更改的空间是故意的,并且如果一致地使用,则仅限于应该的内容改变。

我有时也会将方法设为最终,但不是经常。我很少把课程定稿。我一般这样做是因为我没有必要这样做。我通常不会使用很多继承。我更喜欢使用接口和对象组合 - 这也适用于我发现通常更容易测试的设计。当您对接口而不是具体类进行编码时,在使用jMock等框架测试时, 不需要使用继承,使用接口创建模拟对象要容易得多它是具体的课程。

我想我应该让我的大部分课程最终成绩,但我还没有进入习惯。

答案 10 :(得分:4)

我必须为我的工作阅读很多代码。在实例变量上缺少最终结果是让我烦恼的最重要的事情之一,并使得理解代码变得不必要。对于我的钱,最终的局部变量导致比清晰度更混乱。该语言应该被设计为默认,但我们必须忍受错误。有时它特别适用于循环和if-else树的明确赋值,但大多数情况下它往往表明你的方法过于复杂。

答案 11 :(得分:3)

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

Effective Java 在此(项目15)上有一个完整的项目,指出了意外继承的缺陷。实际上,如果您没有为继承设计和记录您的类,继承它可能会产生意想不到的问题(该项提供了一个很好的示例)。因此,建议您对任何不打算从中继承的类和/或方法使用 final

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

答案 12 :(得分:3)

另一个警告是,很多人混淆final意味着实例变量的内容不能改变,而不是引用不能改变。

答案 13 :(得分:2)

选择为每个方法中的每个参数键入final会对编码员和代码阅读器产生如此大的刺激。

一旦刺激超出合理的转换到Scala,默认情况下参数是最终的。

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

答案 14 :(得分:2)

即使对于局部变量,知道它被声明为final也意味着我不必担心稍后会更改引用。这意味着在调试时我会在稍后看到该变量,我相信它指的是同一个对象。在寻找错误时,我不必担心这一点。 一个好处是,如果99%的变量被宣布为最终变量,那么真正变量的少数变量会更好。 此外,最后一个让编译器找到一些可能会被忽视的愚蠢错误。

答案 15 :(得分:1)

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

强烈推荐。

查看Eclipse Save Actions的my blog post

答案 16 :(得分:1)

我已经编写了一段时间并且尽可能地使用final。在做了一段时间后(对于变量,方法参数和类属性),我可以说90%(或更多)我的变量实际上是最终的。我认为当你不想修改变量时没有修改变量的好处(我之前看到过,而且有时很麻烦)会为代码中的额外输入和额外的“最终”关键字付出代价。

话虽如此,如果我要设计一种语言,我会将每个变量设为final,除非被其他关键字修改。

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

我还使用Collections.unmodifiable ...在需要时创建不可修改的列表。

答案 17 :(得分:1)

对于论据,我认为他们不需要。 Mostley他们只是伤害了可读性。重新分配一个参数变量是如此愚蠢,以至于我应该相信它们可以被视为常量。

Eclipse将最终颜色变为红色这一事实使得更容易在代码中发现变量声明,我认为这些声明在大多数情况下会提高读取性。

我试图强制执行任何和所有变量都应该是最终的规则,因为没有极端的有效理由。回答“这个变量是什么?”要容易得多。问题,如果你只是必须找到启动并确信它就是它。

我实际上现在对非最终变量感到相当紧张。这就像把刀挂在头上的一根线上,或者只是把它放在你的厨房抽屉之间的差异......

最终变量只是一种很好的标记值的方法。

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

一个很好的特性是,当大多数时候选择在算法中使用变量时,sollution就是编写一个方法,这通常可以显着改善代码。

答案 18 :(得分:1)

我从不在局部变量上使用它们,添加的详细程度没有什么意义。即使您不认为该变量应该被重新分配,这对于下一个改变该代码的人来说也没有什么区别,并且由于代码正在被更改,因此使其成为最终的任何原始目的可能不再有效。如果只是为了清晰起见,我认为由于冗长的负面影响而失败。

同样适用于成员变量,因为它们提供的好处很少,除了常量的情况。

它也不影响不变性,因为不可变的东西的最佳指标是它被记录为这样和/或没有可以改变对象的方法(这一点,同时使类最终是唯一的方法保证它是不可变的。)

但是,嘿,这只是我的观点: - )

答案 19 :(得分:1)

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

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

当final与方法一起使用时,它不允许该方法被子类覆盖。

一旦使用非常清楚,应谨慎使用。它主要取决于设计,因为使用final方法不会有助于多态性。

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

使用引用变量,仅在需要对特定对象的不可变引用时才使用它。

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

答案 20 :(得分:1)

我很难在方法或类上使用final,因为我喜欢让人们覆盖它们。

否则,如果它是public/private static final type SOME_CONSTANT;

,我只会使用finally

答案 21 :(得分: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();
    }
}

答案 22 :(得分:0)

我将它用于方法内外的常量。

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

就类而言,仅针对某些基础结构类,我使用了最终类。

IntelliJ IDEA会在函数内写入函数参数时发出警告。所以,我已经停止使用final作为函数参数。我也没有在java Runtime库中看到它们。

答案 23 :(得分: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
        }
    };
}

答案 24 :(得分:-1)

强烈建议使用最终作为常量。但是,我不会将它用于方法或类(或至少考虑它一段时间),因为它使测试更难,如果不是不可能的话。如果你绝对必须让一个类或方法最终,请确保这个类实现了一些接口,这样你就可以有一个 mock 实现相同的接口。