当在方法参数上使用final
关键字时,我不能理解它在哪里真正方便。
如果我们排除匿名类的使用,可读性和意图声明,那么它对我来说似乎几乎毫无价值。
强制要求某些数据保持不变并不像看起来那么有力。
考虑下面这个简单的测试示例。这个测试通过了,尽管该方法更改了提供给它的引用的值,但它没有任何效果。
public void testNullify() {
Collection<Integer> c = new ArrayList<Integer>();
nullify(c);
assertNotNull(c);
final Collection<Integer> c1 = c;
assertTrue(c1.equals(c));
change(c);
assertTrue(c1.equals(c));
}
private void change(Collection<Integer> c) {
c = new ArrayList<Integer>();
}
public void nullify(Collection<?> t) {
t = null;
}
发布于 2012-04-30 16:20:56
停止变量的重新赋值
虽然这些答案在智力上很有趣,但我没有读过简短而简单的答案:
当您希望编译器防止变量被重新赋值给不同的对象时,可以使用关键字
。
无论变量是静态变量、成员变量、局部变量还是实参/参数变量,效果都是完全相同的。
示例
让我们来看看实际效果。
考虑这个简单的方法,其中两个变量(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.
}
将局部变量标记为最终变量。这会导致编译器错误。
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 =
这一行。既然人类会犯错,程序员也是人,让我们请求编译器来帮助我们。将每个参数/实参变量标记为'final‘,以便编译器可以找到并标记任何这样的重新赋值。
回顾
如other answers…中所述考虑到Java最初的设计目标是帮助程序员避免愚蠢的错误,比如读过数组的结尾,Java应该被设计为自动将所有参数/实参变量强制为“final”。换句话说,参数不应该是变量。但是后见之明是20/20的远见,Java设计者当时忙得不可开交。
那么,总是将final
添加到所有参数吗?
我们是否应该将final
添加到每个声明的方法参数中?
仅当方法的代码过长或复杂时才添加final
,其中的参数可能被误认为是局部变量或成员变量,也可能是re-assigned.
如果您采用从不重新赋值参数的做法,您将倾向于为每个参数添加一个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
Java16引入了新的records特性。记录是定义一个类的一种非常简短的方法,该类的中心目的仅仅是以不变和透明的方式携带数据。
您只需声明类名及其成员字段的名称和类型。编译器隐式地提供构造函数、getters、equals
& hashCode
和toString
。
这些字段是只读的,没有setter。因此,record
是一种不需要将参数标记为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;
}
}
发布于 2009-02-02 05:18:29
有时候,显式地(为了可读性)变量不变是很好的。下面是一个简单的示例,使用final
可以省去一些令人头疼的问题:
public void setTest(String test) {
test = test;
}
如果你在setter中忘记了'this‘关键字,那么你想要设置的变量就不会被设置。但是,如果在参数上使用final
关键字,则会在编译时捕获错误。
发布于 2009-02-01 09:54:46
是的,除了匿名类,可读性和意图声明,它几乎一文不值。然而,这三样东西是一文不值的吗?
就我个人而言,我倾向于不将final
用于局部变量和参数,除非我在匿名内部类中使用该变量,但我肯定可以理解那些希望明确说明参数值本身不会改变的人的观点(即使它引用的对象改变了它的内容)。对于那些发现这增加了可读性的人来说,我认为这是一个完全合理的做法。
如果有人真的声称它确实以一种不是这样的方式保持数据不变,那么你的观点就更重要了--但我不记得看到过这样的说法。你是在暗示有大量的开发人员认为final
的效果比它实际的效果更好吗?
编辑:我真的应该用Monty Python参考来总结所有这些;这个问题似乎有点类似于问“罗马人曾经为我们做过什么?”
https://stackoverflow.com/questions/500508
复制相似问题