停止变量的重新分配
虽然这些答案在智力上很有趣,但我还没有读过简短的简单答案:
使用关键字final当你希望编译器阻止
变量被重新分配给不同的对象。
无论变量是静态变量、成员变量、局部变量还是自变量/参数变量,效果都是完全相同的。
Example
让我们看看实际效果。
考虑这个简单的方法,其中两个变量 (arg and x) 都可以重新分配不同的对象。
// Example use of this method:
// this.doSomething( "tiger" );
void doSomething( String arg ) {
String x = arg; // Both variables now point to the same String object.
x = "elephant"; // This variable now points to a different String object.
arg = "giraffe"; // Ditto. Now neither variable points to the original passed String.
}
将局部变量标记为final。这会导致编译器错误。
void doSomething( String arg ) {
final String x = arg; // Mark variable as 'final'.
x = "elephant"; // Compiler error: The final local variable x cannot be assigned.
arg = "giraffe";
}
相反,我们将参数变量标记为final。这也会导致编译器错误。
void doSomething( final String arg ) { // Mark argument as 'final'.
String x = arg;
x = "elephant";
arg = "giraffe"; // Compiler error: The passed argument variable arg cannot be re-assigned to another object.
}
故事的道德启示:
如果要确保变量始终指向同一个对象,
标记变量final.
切勿重新分配参数
作为良好的编程实践(任何语言),您应该never将参数/参数变量重新分配给调用方法传递的对象以外的对象。在上面的例子中,永远不应该写这一行arg =
。既然人类都会犯错误,而程序员也是人,那么我们就让编译器来帮助我们吧。将每个参数/参数变量标记为“最终”,以便编译器可以找到并标记任何此类重新分配。
回想起来
正如其他答案中指出的那样……
鉴于 Java 最初的设计目标是帮助程序员避免读取超出数组末尾的愚蠢错误,Java 应该被设计为自动强制所有参数/参数变量为“final”。换句话说,参数不应该是变量。但事后看来,这是 20/20 的愿景,Java 设计者当时正忙得不可开交。
所以,总是添加final
所有的论点?
我们应该添加final
声明的每个方法参数?
- 理论上是的。
- 实际上,没有。
➥ Add final
仅当方法的代码很长或很复杂时,参数可能会被误认为是局部变量或成员变量,并且可能会被重新分配。
如果您坚持从不重新分配参数的做法,您将倾向于添加一个final
每一个。但这很乏味,并且使声明有点难以阅读。
对于简短的简单代码,其中参数显然是一个参数,而不是局部变量或成员变量,我不费心添加final
。如果代码非常明显,我或任何其他程序员都不可能在进行维护或重构时意外地将参数变量误认为是参数以外的东西,那么就不必费心了。在我自己的工作中,我添加了final
仅在较长或涉及较多的代码中,参数可能会被误认为局部变量或成员变量。
#为了完整性添加了另一个案例
public class MyClass {
private int x;
//getters and setters
}
void doSomething( final MyClass arg ) { // Mark argument as 'final'.
arg = new MyClass(); // Compiler error: The passed argument variable arg cannot be re-assigned to another object.
arg.setX(20); // allowed
// We can re-assign properties of argument which is marked as final
}
record
Java 16 带来了新的records https://openjdk.java.net/jeps/395特征。记录是定义类的一种非常简短的方式,其中心目的只是不可变且透明地携带数据。
您只需声明类名及其成员字段的名称和类型。编译器隐式提供了构造函数、getters、equals
& hashCode
, and toString
.
这些字段是只读的,没有设置器。所以一个record
是一种不需要标记参数的情况final
。它们实际上已经是最终版本。事实上,编译器禁止使用final
当声明记录的字段时。
public record Employee( String name , LocalDate whenHired ) // ???? Marking `final` here is *not* allowed.
{
}
如果您提供可选的构造函数,那么您can mark final
.
public record Employee(String name , LocalDate whenHired) // ???? Marking `final` here is *not* allowed.
{
public Employee ( final String name , final LocalDate whenHired ) // ???? Marking `final` here *is* allowed.
{
this.name = name;
whenHired = LocalDate.MIN; // ???? Compiler error, because of `final`.
this.whenHired = whenHired;
}
}