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

时间:2008-09-30 18:25:04

标签: java final

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

一方面,编译器可以进行一些优化,这使得程序员的意图更加清晰。另一方面,它增加了详细程度,优化可能是微不足道的。

我应该努力记住这件事吗?

16 个答案:

答案 0 :(得分:163)

痴迷:

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

考虑但明智地使用:

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

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

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

答案 1 :(得分:44)

  

我应该努力记住这件事吗?

不,如果您使用的是Eclipse,因为您可以将Save Action配置为自动为您添加这些 final 修饰符。然后,您可以轻松获得收益。

答案 2 :(得分:15)

“最终”的开发时间优势至少与运行时间优势一样重要。它告诉未来的编辑代码有关你的意图。

将类标记为“final”表示您在设计或实现类时没有努力优雅地处理扩展。如果读者可以对课程进行更改,并希望删除“最终”修饰符,则他们可以自行承担风险。他们应该确保班级能够很好地处理扩展。

标记变量“final”(并在构造函数中赋值)对依赖项注入很有用。它表示变量的“协作者”性质。

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

答案 3 :(得分:9)

我发现标记方法参数和本地作为final可用作重构辅助工具,因为有问题的方法是几页长的难以理解的混乱。自由地散布final,看看编译器(或你的IDE)抛出的“无法分配给最终变量”的错误,你可能会发现为什么名为“data”的变量最终为null,即使有几个(过时) )评论发誓不可能发生。

然后,您可以通过将重用的变量替换为更接近使用点的新变量来修复一些错误。然后你会发现你可以在方括号中包装方法的整个部分,突然间你只需要一个远离“提取方法”的IDE键盘,你的怪物就会更容易理解。

如果你的方法已经是一个难以维护的残骸,我想最终可能会有一些东西可以阻止人们把它变成残骸;但如果它是一个简短的方法(参见:不是不可维护的),那么你冒着增加大量冗长的风险。特别是,Java函数签名非常难以适应80个字符,而不是每个参数增加6个字符!

答案 4 :(得分:8)

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

因此,您应该尝试在使用条件时始终(IMHO)使用最终变量。

让我举个例子:

    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可以使Java与Lisp的let表达式非常相似,并且使得代码不会大量缩进(因为词法作用域变量)。

正如@Recurse注意到的那样(但显然是-1我)你可以完成上述操作而不使String name final得到编译器错误(我从来没有说过你不能)但是你可能很容易使编译器错误消失,在switch语句抛出表达式语义之后设置名称,或者更糟糕的是忘记break你不能导致错误(尽管@Recurse说的话)而不使用final

    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";

最终变量强制单一评估应该是什么名称。类似于具有返回值的函数必须始终返回一个值(忽略异常),名称切换块必须解析名称,从而绑定到该开关块,这使得重构代码块变得更容易(即Eclipe重构:提取方法)

上面的OCaml:

type plugin = CandidateExport | JobPostingImport

let p = CandidateExport

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

match ... with ...的评估类似于函数,即表达式。注意它看起来像我们的switch语句。

这是Scheme(球拍或鸡肉)中的一个例子:

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

答案 5 :(得分:6)

嗯,这一切都取决于你的风格......如果你想看到最终你不会修改变量,那就用它吧。如果你不喜欢看到它......那就把它留下吧。

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

我更喜欢动态语言,所以我喜欢避免冗长并不奇怪。

所以,我会说只是选择你倾向的方向,然后选择它(无论如何,尽量保持一致)。


作为旁注,我参与了使用和不使用这种模式的项目,我发现错误或错误的数量没有差别...我不认为这是一种模式这将极大地改善你的错误数量或任何东西,但它又是风格,如果你想表达你不会修改它的意图,那么继续使用它。

答案 6 :(得分:6)

在参数中有用的是避免意外更改参数值并引入一个微妙的错误。我用了忽略这个建议,但花了4个小时后。在一个可怕的方法(有数百行代码和多个fors,嵌套ifs和各种不良做法)我建议你这样做。

 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 ) { ...

 }

当然,在一个完美的世界中,这不会发生,但是......好吧..有时你必须支持其他代码。 :(

答案 7 :(得分:5)

如果你正在编写一个应用程序,有人必须在1年之后阅读代码,那么是的,使用final不应该一直修改的变量。通过这样做,您的代码将更加“自我记录”,并且您还可以减少其他开发人员使用本地常量作为本地临时变量等愚蠢行为的机会。

如果你正在写一些一次性代码,那么,不,不要费心去识别所有的常数并让它们成为最终的。

答案 8 :(得分:4)

我会尽可能多地使用final。如果您无意中更改了字段,则会执行此操作。我还将Method参数设置为final。这样做我已经从代码中找到了一些错误,当我们尝试“设置”一个忘记Java传递值的参数时,我接管了这些错误。

答案 9 :(得分:2)

从问题中不清楚这是否显而易见,但制作方法参数final只影响方法的主体。它 NOT 向调用者传达有关方法意图的任何有趣信息。传入的对象仍然可以在方法中变异(决赛不是consts),变量的范围在方法中。

要回答你的确切问题,除非代码需要它(例如,变量是从内部类引用),或者澄清一些非常复杂的逻辑,否则我不会费心去做一个实例或局部变量(包括方法参数)final。

对于实例变量,如果它们是逻辑常数,我会将它们作为最终变量。

答案 10 :(得分:2)

变量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变量,该变量为参数environmentKey添加前缀。在这种情况下,最终变量仅在执行范围内是最终的,在每次执行该方法时都是不同的。每次输入方法时,都会重建最终结果。一旦构造,就不能在方法执行的范围内更改它。这允许您在方法的持续时间内修改方法中的变量。见下文:

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:

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

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

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

如果发现它确实是最终版本,那么将它标记为最终可以省去麻烦,以展示这个Test类。乍一看看起来很公开。

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

不幸的是,由于该类的唯一构造函数是私有的,因此无法扩展此类。对于Test类,没有理由认为该类应该是final。 Test类是隐式final类如何导致问题的一个很好的例子。

因此,当您通过使其构造函数为私有来隐式地使类成为最终时,您应该将其标记为最终。

答案 11 :(得分:1)

如你所说,有些权衡取舍,但我更喜欢明确使用某些内容而不是隐式使用。这将有助于消除未来代码维护者的一些歧义 - 即使只是你。

答案 12 :(得分:1)

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

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

答案 13 :(得分:0)

如果您将变量设为final

,请为变量使用immutable关键字

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

在Java 8发布版中,我们还有一个名为“ effectively final variable ”的概念。 非最终变量可以作为最终变量。

从lambda表达式引用的局部变量必须是最终的或有效的最终

  

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

直到Java 7,您不能在匿名类中使用非最终局部变量,但是从Java 8中可以

看看这个article

答案 14 :(得分:0)

一个非常简单的答案我们有3个案例的最终变量,最终的方法&amp;&amp;最后的课程..

1.Final with variable:你不能多次分配这个变量..

2.Final with methods:你不能覆盖这个方法..

3.Final with classes:你不能扩展任何最终类

答案 15 :(得分:-1)

首先,final关键字用于使变量保持不变。常数意味着它不会改变。例如:

final int CM_PER_INCH = 2.54;

你会声明变量final,因为每英寸厘米不会改变。

如果您尝试覆盖最终值,则该变量首先被声明。例如:

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