停止变量的重新分配
虽然这些答案在智力上很有趣,但我还没有阅读简短的简单答案:
当您希望编译器防止将变量重新分配给不同的对象时,请使用关键字final 。
无论变量是静态变量、成员变量、局部变量还是自变量/参数变量,效果都是完全一样的。
例子
让我们看看实际效果。
考虑这个简单的方法,其中两个变量(arg和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。
从不重新分配参数
作为良好的编程习惯(在任何语言中),您不应该将参数/参数变量重新分配给调用方法传递的对象以外的对象。在上面的例子中,永远不要写这一行arg =
。既然人类会犯错,而程序员也是人,那就请编译器来帮助我们吧。将每个参数/参数变量标记为“最终”,以便编译器可以找到并标记任何此类重新分配。
回想起来
正如其他答案中所指出的......鉴于Java的最初设计目标是帮助程序员避免愚蠢的错误,例如读取数组末尾的内容,Java应该被设计为自动将所有参数/参数变量强制执行为“最终”。换句话说,Arguments 不应该是 variables。但事后看来是 20/20 的愿景,当时 Java 设计人员忙得不可开交。
所以,总是添加final
到所有的论点?
我们应该添加final
到每个声明的方法参数吗?
- 理论上,是的。
- 在实践中,没有。
➥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 带来了新的记录功能。记录是定义一个类的一种非常简短的方法,该类的中心目的只是以不可变和透明的方式携带数据。
您只需声明类名及其成员字段的名称和类型。编译器隐式提供构造函数、getter、equals
&hashCode
和toString
.
这些字段是只读的,没有设置器。所以 arecord
是一种不需要标记参数的情况final
。它们实际上已经是最终的了。实际上,编译器final
在声明记录的字段时禁止使用。
public record Employee( String name , LocalDate whenHired ) // Marking `final` here is *not* allowed.
{
}
如果你提供一个可选的构造函数,你可以在那里标记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;
}
}