我不明白在方法参数上使用final关键字时,它在哪里真正方便
如果我们排除匿名类、可读性和意图声明的使用,那么对我来说几乎毫无价值
强制某些数据保持不变并不像看上去的那么强烈
-
如果该参数是一个原语,那么它将不起作用,因为该参数作为值传递给该方法,并且在范围之外更改它将不会产生任何影响
-
如果我们通过引用传递一个参数,那么引用本身就是一个局部变量,如果引用是从方法内部更改的,那么从方法范围之外不会有任何影响
考虑下面的简单测试示例。
该测试通过,尽管该方法更改了给定给它的参考值,但没有效果
公共void testNullify(){
集合<;整数>;c=新数组列表<;整数>;();
取消(c);
资产净值(c);
最终收集<;整数>;c1=c;
资产真(c1等于(c));
改变(c);
资产真(c1等于(c));
}
私有无效更改(集合<;整数>;c){
c=新数组列表<;整数>;();
}
公共无效作废(集合<;?>;t){
t=零;
}
停止变量的重新分配
虽然这些答案在智力上很有趣,但我没有读过简单的简短答案:
当您希望编译器防止出现错误时,请使用关键字final
将变量重新指定给不同的对象
无论变量是静态变量、成员变量、局部变量还是参数/参数变量,效果都是完全相同的
范例
让我们看看效果如何
考虑这个简单的方法,其中两个变量(arg和x)都可以重新分配给不同的对象
//此方法的示例使用:
//这是一只老虎;
void doSomething(字符串参数){
String x=arg;//两个变量现在都指向同一个字符串对象。
x=";大象";//此变量现在指向不同的字符串对象。
arg=";giraffe";//同上。现在两个变量都不指向原始传递的字符串。
}
将局部变量标记为final。这将导致编译器错误
void doSomething(字符串arg){
final字符串x=arg;//将变量标记为“final”。
x=";大象";//编译器错误:无法分配最终的局部变量x。
arg=“长颈鹿”;;
}
相反,让我们将参数变量标记为final。这也会导致编译器错误
void doSomething(final String arg){//将参数标记为'final'。
字符串x=arg;
x=“大象”;;
arg=";giraffe";//编译器错误:无法将传递的参数变量arg重新分配给另一个对象。
}
故事的寓意:
如果要确保变量始终指向同一对象,
将变量标记为final
永远不要重新分配参数
作为良好的编程实践(在任何语言中),您都应该绝不将参数/参数变量重新分配给调用方法传递的对象以外的对象。在上面的例子中,永远不要写行arg=。既然人类会犯错误,而程序员也是人类,那么让我们请编译器来帮助我们。将每个参数/参数变量标记为“final”,以便编译器可以找到并标记任何此类重新赋值
回顾
如其他答复所述…
考虑到Java最初的设计目标是帮助程序员避免愚蠢的错误,例如读取数组末尾,Java应该被设计为自动强制所有参数/参数变量为“final”。换句话说,参数不应该是变量。但事后诸葛亮是20/20的愿景,Java设计师当时忙得不可开交
那么,是否总是将final添加到所有参数中
我们是否应该将final添加到所声明的每个方法参数中
- 理论上,是的
- 实际上,没有。
➥ 仅当方法的代码很长或很复杂时才添加final,此时参数可能被误认为是局部变量或成员变量,并可能被重新赋值
如果你接受从不重新分配参数的做法,你会倾向于在每个参数中添加一个final。但这很乏味,而且使声明更难阅读
对于参数显然是参数而不是局部变量或成员变量的简单代码,我不需要添加final。如果代码非常明显,我和任何其他进行维护或重构的程序员都不会意外地将参数变量误认为是参数以外的东西,那么就不用麻烦了。在我自己的工作中,我只在较长或更复杂的代码中添加了final,其中一个参数可能被误认为是局部变量或成员变量
#为完整性增加了另一个案例
公共类MyClass{
私人INTX;
//接球手和接球手
}
void doSomething(final MyClass arg){//将参数标记为“final”。
arg=new MyClass();//编译器错误:无法将传递的参数变量arg重新分配给另一个对象。
arg.setX(20);//允许
//我们可以重新分配标记为final的参数的属性
}
记录
Java 16带来了新的记录功能。记录是定义类的一种非常简单的方法,它的中心目的仅仅是不可变和透明地传输数据
只需声明类名及其成员字段的名称和类型。编译器隐式地提供构造函数、getter、等于hashCode和toString
字段是只读的,没有设置符。因此,记录是一种不需要标记参数的情况。它们实际上已经是最终的了。实际上,编译器禁止在声明记录的字段时使用final
public record Employee(字符串名称,雇用时的LocalDate)//🡄 不允许在此处标记“final”。
{
}
如果提供可选构造函数,则可以在那里标记final
public record Employee(字符串名称,雇用时的LocalDate)//🡄 不允许在此处标记“final”。
{
公共员工(最终字符串名称、最终本地日期(如果需要)//🡄 允许在此处*标记'final'。
{
this.name=名称;
whenHired=LocalDate.MIN;//🡄 编译器错误,因为“final”。
this.whenHired=whenHired;
}
}