什么时候应该对方法参数和局部变量使用 final?

我发现一些参考资料(比如说)建议尽可能多地使用 final,我想知道这有多重要。这主要是在方法参数和局部变量的上下文中,而不是在最终方法或类的上下文中。对于常量来说,这是显而易见的。

一方面,编译器可以进行一些优化,使程序员的意图更加清晰。另一方面,它增加了冗长性,而且优化可能是微不足道的。

我是不是应该努力回忆一下?

62959 次浏览

我是不是应该努力记住这件事?

不,如果您正在使用 Eclipse,因为您可以配置一个 SaveAction 来自动为您添加这些 期末考试修饰符。然后你得到的好处更少的努力。

嗯,这完全取决于你的风格... 如果你喜欢看到最终的时候,你不会修改变量,然后使用它。如果你不喜欢看,那就别看了。

我个人喜欢尽可能少的冗长,所以我倾向于避免使用不必要的额外关键字。

不过我更喜欢动态语言,所以我喜欢避免冗长也就不足为奇了。

所以,我会说只要选择你倾向的方向,然后跟着它走(不管是什么情况,试着保持一致)。


顺便说一句,我做过的项目都使用和不使用这样的模式,我没有看到任何差异的错误或错误的数量... 我不认为这是一个模式,将大大提高你的错误数量或任何东西,但它是风格,如果你喜欢表达的意图,你不会修改它,然后继续使用它。

正如您所提到的,这是一种权衡,但我更喜欢显式使用而不是隐式使用。这将有助于消除未来代码维护人员的一些模糊性——即使只是您自己。

如果您有内部(匿名)类,并且该方法需要访问包含该方法的变量,则需要将该变量作为 final。

除此之外,你说的都是对的。

“ final”在开发时的好处至少与运行时的好处同样重要。它将告诉代码的未来编辑器有关您的意图的一些信息。

将类标记为“ final”表示您在设计或实现该类期间没有做出努力来优雅地处理扩展。如果读者可以对类进行更改,并希望删除“ final”修饰符,那么他们可以自担风险。这取决于他们如何确保这门课能够很好地处理扩展。

标记一个变量“ final”(并在构造函数中赋值)对依赖注入非常有用。它指出了变量的“合作者”特性。

标记方法“ final”在抽象类中很有用,它清楚地描述了扩展点的位置。

迷恋:

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

考虑一下,但要明智地使用:

  • 最终类——框架/API 设计是我唯一考虑它的情况。
  • Final 方法-基本上与 Final 类相同。如果您正在使用模板方法模式,比如疯狂地将内容标记为 final,那么您可能过于依赖继承,而对委托的依赖不够。

忽略,除非感到肛交:

  • 方法参数和局部变量-我很少这样做,主要是因为我懒惰,我发现它杂乱的代码。我会完全承认,标记参数和局部变量,我不会修改是“正确的”。我希望这是默认的。但事实并非如此,而且我发现期末考试期间的代码更加难以理解。如果我在别人的代码中,我不会把它们拉出来,但是如果我在写新代码,我不会把它们放进去。一个例外是,您必须标记某个 final,以便可以从匿名内部类中访问它。

  • 编辑: 注意一个用例,其中最终的局部变量实际上非常有用,正如 @ adam-gent所提到的,那就是当 value 被赋给 if/else分支中的 var 时。

如果你正在编写一个应用程序,有人将不得不读代码后,比如说,1年,然后是的,使用 final on 变量,不应该被修改所有的时间。通过这样做,您的代码将更加“自我文档化”,并且您还减少了其他开发人员做一些愚蠢的事情的机会,比如使用局部常量作为局部临时变量。

如果你正在编写一些一次性代码,那么,不用麻烦去识别所有的常量并使它们成为最终的。

我会尽可能多地使用期末考试。如果无意中更改了字段,则这样做将标记。我还将 Method 参数设置为 final。这样做,我已经抓住了几个错误,当他们试图“设置”一个参数忘记 Java 传递的值时,我已经接管的代码。

从这个问题中还不清楚这是否是显而易见的,但是使方法参数成为 final 只会影响方法的主体。没有确实向调用者传达了关于方法意图的任何有趣信息。传入的对象仍然可以在方法中进行变更(final 不是 const) ,变量的作用域在方法中。

为了回答你的确切问题,我不会费心让一个实例或局部变量(包括方法参数)最终,除非代码需要它(例如,变量是从内部类引用的) ,或澄清一些真正复杂的逻辑。

对于实例变量,如果它们是逻辑常量,我会使它们成为 final。

我发现,当有问题的方法是一个长达几页的令人费解的混乱方法时,将方法参数和局部变量标记为 final作为重构辅助工具是非常有用的。随意地使用 final,看看编译器(或 IDE)抛出了什么“无法赋值给最终变量”的错误,你可能会发现为什么称为“ data”的变量以 null 结束,尽管一些(过时的)注释断言这是不可能发生的。

然后,您可以通过将重用的变量替换为更接近于使用点的声明的新变量来修复一些错误。然后你发现你可以把整个方法包装成大括号,突然间你离“提取方法”只差一个 IDE 键,你的怪物变得更容易理解了。

如果你的方法是 没有已经是一个无法维护的残骸了,我想最终的东西可能是有价值的,以阻止人们把它变成所说的残骸; 但如果它是一个简短的方法(见: 不是无法维护) ,那么你就有增加很多冗长的风险。特别是,Java 函数签名很难容纳80个字符,因为它不需要为每个参数增加6个字符!

在参数中,避免意外地更改参数值并引入一个微妙的错误是非常有用的。我过去常常忽略这个建议,但是在花了4个小时之后。在一个糟糕的方法中(有数百行代码和多个 fors、嵌套的 if 和各种各样的错误做法) ,我建议你这样做。

 public int processSomethingCritical( final int x, final int y ){
// hundreds of lines here
// for loop here...
int x2 = 0;
x++; // bug aarrgg...
// hundreds of lines there
// if( x == 0 ) { ...


}

当然,在一个完美的世界里,这种情况是不会发生的,但是... ... 好吧... ... 有时候你必须支持其他人的代码

变量 final有许多用途

最终常数

 public static class CircleToolsBetter {
public final static double PI = 3.141;
public double getCircleArea(final double radius) {
return (Math.pow(radius, 2) * PI);
}
}

这可以用于您的代码的其他部分,或由其他类访问,这样,如果您想要更改值,您就不必一个接一个地更改它们。

最终变量

public static String someMethod(final String environmentKey) {
final String key = "env." + environmentKey;
System.out.println("Key is: " + key);
return (System.getProperty(key));


}


}

在此类中,您将构建一个有作用域的 final 变量,该变量将前缀添加到参数環 Key 中。在这种情况下,final 变量仅在执行范围内为 final,这在方法的每次执行中是不同的。每次输入方法时,都会重新构造 final。一旦构造完成,就不能在方法执行的范围内对其进行更改。这允许您在方法的持续时间内修复方法中的变量。见下文:

public class FinalVariables {




public final static void main(final String[] args) {
System.out.println("Note how the key variable is changed.");
someMethod("JAVA_HOME");
someMethod("ANT_HOME");
}
}

最终常数

public double equation2Better(final double inputValue) {
final double K = 1.414;
final double X = 45.0;


double result = (((Math.pow(inputValue, 3.0d) * K) + X) * M);
double powInputValue = 0;
if (result > 360) {
powInputValue = X * Math.sin(result);
} else {
inputValue = K * Math.sin(result);   // <= Compiler error
}

当你有很长的代码行时,这些特别有用,它会产生编译器错误,所以当有人意外地改变了不应该改变的变量时,你不会遇到逻辑/业务错误。

最终收藏

当我们讨论集合时,不同的情况下,您需要将它们设置为不可修改的。

 public final static Set VALID_COLORS;
static {
Set temp = new HashSet( );
temp.add(Color.red);
temp.add(Color.orange);
temp.add(Color.yellow);
temp.add(Color.green);
temp.add(Color.blue);
temp.add(Color.decode("#4B0082")); // indigo
temp.add(Color.decode("#8A2BE2")); // violet
VALID_COLORS = Collections.unmodifiableSet(temp);
}

否则,如果你不把它设置为不可修改的:

Set colors = Rainbow.VALID_COLORS;
colors.add(Color.black); // <= logic error but allowed by compiler

Final Class 最终方法分别不能被扩展或覆盖。

编辑: 为了解决最后一类有关封装的问题:

期末考试有两种方法。第一种方法是在类声明中使用关键字 final:

public final class SomeClass {
//  . . . Class contents
}

使类为 final 的第二种方法是将其所有构造函数声明为 private:

public class SomeClass {
public final static SOME_INSTANCE = new SomeClass(5);
private SomeClass(final int value) {
}

如果您发现它实际上是一个 final,那么将其标记为 final 可以省去您的麻烦,以便在 Test 类中进行演示。第一眼看上去很公开。

public class Test{
private Test(Class beanClass, Class stopClass, int flags)
throws Exception{
//  . . . snip . . .
}
}

不幸的是,由于该类的唯一构造函数是私有的,因此不可能扩展该类。在 Test 类的情况下,没有理由该类应该是 final 类。Test 类是隐式 final 类如何导致问题的一个很好的例子。

因此,当通过将类的构造函数设置为 private 隐式地使类成为 final 时,应该将其标记为 final。

首先,final 关键字用于创建一个变量常量。常数意味着它不会改变。例如:

final int CM_PER_INCH = 2.54;

您将声明变量 final,因为每英寸1厘米不变。

如果尝试覆盖一个最终值,那么该变量就是首先声明的变量。例如:

final String helloworld = "Hello World";
helloworld = "A String"; //helloworld still equals "Hello World"

有一个编译错误,类似于:

local variable is accessed from inner class, must be declared final

如果您的变量不能声明为 final,或者您不想声明为 final,可以这样做:

final String[] helloworld = new String[1];
helloworld[0] = "Hello World!";
System.out.println(helloworld[0]);
helloworld[0] = "A String";
System.out.println(helloworld[0]);

这将印刷:

Hello World!
A String

我一直使用 final使 Java 更基于表达式。请参阅 Java 的条件(if,else,switch)不是基于表达式的,我一直很讨厌这种情况,特别是如果你习惯了函数式编程(比如 ML、 Scala 或 Lisp)。

因此,当使用条件时,您应该尝试始终(恕我直言)使用最终变量。

举个例子:

    final String name;
switch(pluginType) {
case CANDIDATE_EXPORT:
name = "Candidate Stuff";
break;
case JOB_POSTING_IMPORT:
name = "Blah";
break;
default:
throw new IllegalStateException();
}

现在,如果添加另一个 case语句并且不设置 name,编译器将失败。如果没有在每个情况下(设置变量)中断,编译器也会失败。这使得 Java 非常类似于 Lisp 的 let表达式,并使得代码不会大量缩进(因为词法范围变量)。

正如@Recurse 指出的那样(但显然是 -1 me) ,你可以在不使用 String name final的情况下得到编译器错误(我从未说过你不能) ,但是你可以很容易地让编译器错误消失在 switch 语句之后设置名称,这会丢弃表达式语义,或者更糟糕的是忘记使用 break,如果不使用 final,你不可能导致错误(不管@Recurse 怎么说) :

    String name;
switch(pluginType) {
case CANDIDATE_EXPORT:
name = "Candidate Stuff";
//break; whoops forgot break..
//this will cause a compile error for final ;P @Recurse
case JOB_POSTING_IMPORT:
name = "Blah";
break;
}
// code, code, code
// Below is not possible with final
name = "Whoops bug";

由于错误设置的名称(除了忘记了 break,这也是另一个错误) ,我现在可以意外地这样做:

    String name;
switch(pluginType) {
case CANDIDATE_EXPORT:
name = "Candidate Stuff";
break;
//should have handled all the cases for pluginType
}
// code, code, code
// Below is not possible with final
name = "Whoops bug";

最后一个变量强制对名称应该是什么进行单一计算。类似于有返回值的函数必须总是返回一个值(忽略异常) ,名称开关块必须解析名称,因此绑定到开关块,这使得代码重构块更容易(如 Eclipse 重构: 提取方法)。

OCaml 中的以上内容:

type plugin = CandidateExport | JobPostingImport


let p = CandidateExport


let name = match p with
| CandidateExport -> "Candidate Stuff"
| JobPostingImport -> "Blah" ;;

match ... with ...的计算结果类似于一个函数 ie 表达式。注意它看起来像我们的 switch 语句。

下面是 Scheme (Racket 或 Chicken)中的一个例子:

(define name
(match b
['CandidateExport "Candidate Stuff"]
['JobPostingImport "Blah"]))

如果将变量设置为 immutable,则对变量使用 final关键字

通过将变量声明为 final,它可以帮助开发人员排除在高度多线程环境中可能出现的变量修改问题。

随着 java 8的发布,我们又多了一个概念称为“ 强 > effectively final variable

从 lambda 表达式引用的局部变量必须是 final 或实际上是 final

如果变量在本地块中初始化后未被修改,则将其视为 有效的最终。这意味着您现在可以在匿名类或 lambda 表达式中使用不带 final 关键字的局部变量,前提是它们必须是有效的 final 关键字。

在 Java7之前,您不能在匿名类中使用非最终局部变量,但是在 Java8中可以

看看这个 文章