一切都是最后的

时间:2010-08-24 17:21:15

标签: java final

我一直在使用PMD帮助发现我的Java代码中的潜在问题,我一直在寻找它的建议,分为有用的,特殊的和“WTF?!”。

它一直告诉我要做的一件事就是使用final关键字来表示我可以附加的每个变量,包括输入参数。对于实际的常数,这似乎是明智的,但对于其他东西,它只是让我感到奇怪,甚至可能适得其反。

final挂在每个变量声明上是否有具体的优点/缺点?

5 个答案:

答案 0 :(得分:26)

“你可能的每个变量声明”听起来有点极端,但final实际上在许多方面都是有益的。有时我希望final是默认行为,并且不需要关键字,但真正的“变量”需要variable修饰符。 Scala通过valvar个关键字采用了这种方法,强烈鼓励使用valfinal - 类关键字)。

仔细考虑每个成员变量是finalvolatile还是两者都不是特别重要,因为类的线程安全性取决于是否正确。分配给finalvolatile变量的值始终对其他线程可见,而不使用synchronized块。

对于局部变量,它并不重要,但使用final可以帮助您更清楚地推理代码并避免一些错误。如果您不希望在方法中更改值,请使用final,并让编译器发现未被注意到的违反此期望的行为。我现在还没有意识到这一点,但很容易想到JIT编译器也可以使用这个提示来提高性能。

在实践中,我不会随时声明局部变量final。我不喜欢视觉混乱,看起来很麻烦。但是,这并不意味着它不是我应该做的事情。

已经提议将var关键字添加到Java,旨在支持类型推断。但作为该提案的一部分,已经提出了一些关于指定局部变量不变性的其他方法的建议。例如,一个建议是添加关键字val以声明具有推断类型的不可变变量。或者,有些人主张同时使用finalvar

答案 1 :(得分:5)

final告诉读者,首先分配的值或引用在以后的任何时间都是相同的。

由于在这种情况下,最终的所有内容都是最终的,缺少最终告诉读者 值将在以后更改,并将其考虑在内。< / p>

答案 2 :(得分:2)

这是PMD等工具的常用习惯用法。例如,下面是Checkstyle中的相应规则。这真的是一种风格/偏好的问题,你可以争论双方。

在我看来,使用final作为方法参数和局部变量(如果适用)是很好的风格。 “扩展设计”这个成语值得商榷。

答案 3 :(得分:1)

PMD还有可以启用的选项规则抱怨final;这是一条武断的规则。

如果我正在进行将API导出到另一个团队 - 或者向全世界 - 的项目,请保留PMD规则。如果您正在开发永远且永远是封闭API的东西,请禁用该规则并节省一些时间。

答案 4 :(得分:1)

以下是为什么几乎所有内容都标记为 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类如何导致问题的一个很好的例子。

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