我一直在使用PMD帮助发现我的Java代码中的潜在问题,我一直在寻找它的建议,分为有用的,特殊的和“WTF?!”。
它一直告诉我要做的一件事就是使用final
关键字来表示我可以附加的每个变量,包括输入参数。对于实际的常数,这似乎是明智的,但对于其他东西,它只是让我感到奇怪,甚至可能适得其反。
将final
挂在每个变量声明上是否有具体的优点/缺点?
答案 0 :(得分:26)
“你可能的每个变量声明”听起来有点极端,但final
实际上在许多方面都是有益的。有时我希望final
是默认行为,并且不需要关键字,但真正的“变量”需要variable
修饰符。 Scala通过val
和var
个关键字采用了这种方法,强烈鼓励使用val
(final
- 类关键字)。
仔细考虑每个成员变量是final
,volatile
还是两者都不是特别重要,因为类的线程安全性取决于是否正确。分配给final
和volatile
变量的值始终对其他线程可见,而不使用synchronized
块。
对于局部变量,它并不重要,但使用final
可以帮助您更清楚地推理代码并避免一些错误。如果您不希望在方法中更改值,请使用final
,并让编译器发现未被注意到的违反此期望的行为。我现在还没有意识到这一点,但很容易想到JIT编译器也可以使用这个提示来提高性能。
在实践中,我不会随时声明局部变量final
。我不喜欢视觉混乱,看起来很麻烦。但是,这并不意味着它不是我应该做的事情。
已经提议将var
关键字添加到Java,旨在支持类型推断。但作为该提案的一部分,已经提出了一些关于指定局部变量不变性的其他方法的建议。例如,一个建议是添加关键字val
以声明具有推断类型的不可变变量。或者,有些人主张同时使用final
和var
。
答案 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类如何导致问题的一个很好的例子。
因此,当您通过使其构造函数为私有来隐式地使类成为最终时,您应该将其标记为最终。