为什么我应该在Java中的方法参数上使用关键字“final”?停止变量的重新分配那么,总是添加到所有参数中?finalrecord

我无法理解关键字在方法参数上使用时真正方便的地方。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;
}

答案 1

停止变量的重新分配

虽然这些答案在智力上很有趣,但我还没有读过简短的答案:

如果希望编译器阻止将变量重新分配给其他对象,请使用关键字 final

无论变量是静态变量、成员变量、局部变量还是参数/参数变量,效果都是完全相同的。

让我们看看效果的实际效果。

考虑这个简单的方法,其中两个变量(argx)都可以重新分配不同的对象。

// 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

从不重新分配参数

作为良好的编程实践(在任何语言中),切勿将参数/参数变量重新分配给调用方法传递的对象以外的对象。在上面的示例中,永远不应该写行 。由于人类会犯错误,而程序员也是人类,让我们请编译器来协助我们。将每个参数/参数变量标记为“final”,以便编译器可以找到并标记任何此类重新赋值。arg =

回顾

如其他答案所述...鉴于Java最初的设计目标是帮助程序员避免愚蠢的错误,例如读取数组的末尾,Java应该被设计为自动将所有参数/参数变量强制为“final”。换句话说,参数不应该是变量。但事后看来是20/20的愿景,Java设计师当时已经全力以赴。

那么,总是添加到所有参数中?final

我们是否应该添加到每个要声明的方法参数中?final

  • 从理论上讲,是的。
  • 在实践中,no.
    ➥ 仅当方法的代码很长或很复杂时才添加,其中参数可能被误认为是局部变量或成员变量,并可能重新分配。final

如果您接受从不重新分配参数的做法,您将倾向于为每个参数添加一个。但这很乏味,使声明更难阅读。final

对于参数显然是参数的简短代码,而不是局部变量或成员变量,我懒得添加 .如果代码非常明显,我和任何其他程序员都不可能进行维护或重构,意外地将参数变量误认为不是参数,那么请不要打扰。在我自己的工作中,我只添加更长或更复杂的代码,其中参数可能被误认为是局部或成员变量。finalfinal

为完整性添加了#Another案例

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 带来了新的记录功能。记录是定义类的一种非常简短的方法,其中心目的只是以不可变和透明的方式携带数据。

您只需声明类名及其成员字段的名称和类型。编译器隐式提供构造函数、getters 和 。equalshashCodetoString

这些字段是只读的,没有 setter。因此,a是一种不需要标记参数的情况。它们实际上已经是最终的。实际上,编译器在声明记录的字段时禁止使用。recordfinalfinal

public record Employee( String name , LocalDate whenHired )  // 						

答案 2