【发布时间】:2011-04-03 07:33:06
【问题描述】:
我一直在使用 PMD 来帮助发现我的 Java 代码中的潜在问题,并且我发现它的建议是在有用的、特殊的和“WTF?!”之间进行区分。
它一直告诉我要做的一件事是对我可以附加到的每个变量(包括输入参数)使用 final 关键字。对于实际的常量,这似乎是明智的,但对于其他东西,我觉得它很奇怪,甚至可能适得其反。
在每个变量声明上挂上final 是否有具体的优点/缺点?
【问题讨论】:
我一直在使用 PMD 来帮助发现我的 Java 代码中的潜在问题,并且我发现它的建议是在有用的、特殊的和“WTF?!”之间进行区分。
它一直告诉我要做的一件事是对我可以附加到的每个变量(包括输入参数)使用 final 关键字。对于实际的常量,这似乎是明智的,但对于其他东西,我觉得它很奇怪,甚至可能适得其反。
在每个变量声明上挂上final 是否有具体的优点/缺点?
【问题讨论】:
以下是为什么将几乎所有内容标记为 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 Classes 和 Final Methods 不能分别扩展或覆盖。
编辑:解决关于封装的最后一类问题:
有两种方法可以使类最终化。第一种是在类声明中使用关键字final:
public final class SomeClass {
// . . . Class contents
}
使类成为final的第二种方法是将其所有构造函数声明为私有:
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 类如何导致问题的一个很好的例子。
因此,当您通过将其构造函数设为私有来隐式将类设为 final 时,应将其标记为 final。
【讨论】:
“你可以做的每一个变量声明”听起来有点极端,但final 实际上在很多方面都是有益的。有时我希望final 是默认行为,并且不需要关键字,但真正的“变量”需要variable 修饰符。 Scala 通过 val 和 var 关键字采用了类似的方法——强烈建议使用 val(final-like 关键字)。
仔细考虑每个成员变量是final,volatile,还是两者都不是,这一点尤为重要,因为类的线程安全取决于是否正确。分配给final 和volatile 变量的值始终对其他线程可见,无需使用synchronized 块。
对于局部变量,它并不那么重要,但是使用final 可以帮助您更清楚地推理您的代码并避免一些错误。如果您不希望在方法中更改值,请使用final 说明,并让编译器发现未注意到的违反此期望的违规行为。我目前不知道有什么,但很容易想象 JIT 编译器也可以使用这个提示来提高性能。
在实践中,我不会尽可能声明局部变量final。我不喜欢视觉上的混乱,而且看起来很麻烦。但是,这并不意味着这不是我应该做的事情。
已提出将var 关键字添加到Java 以支持类型推断的建议。但作为该提案的一部分,已经有许多关于指定局部变量不变性的其他方法的建议。例如,一个建议是还添加关键字val 来声明具有推断类型的不可变变量。或者,有些人主张同时使用final 和var。
【讨论】:
final,而 从不 需要;它可以查看每个变量是否实际被修改。当然,它看到的和程序员看到的可能完全不同……
final 告诉读者,首先分配的值或引用在以后的任何时候都是相同的。
由于在这种情况下所有可以是最终的都是最终的,所以 缺少 最终会告诉读者值 稍后会发生变化,并考虑到这一点。
【讨论】:
PMD 还有一些选项规则,你可以打开它来抱怨final;这是一个任意规则。
如果我正在执行一个项目,其中 API 被导出到另一个团队 - 或世界 - 保留 PMD 规则不变。如果您只是在开发将永远是封闭 API 的东西,请禁用该规则并节省一些时间。
【讨论】:
这是 PMD 等工具的常见习语。例如,下面是 Checkstyle 中对应的规则。这真的是风格/偏好的问题,你可以为双方争论。
在我看来,对方法参数和局部变量(如果适用)使用 final 是一种很好的风格。 “为扩展而设计”成语值得商榷。
【讨论】: