为什么我应该使用关键字“final” Java 中的方法参数?
我无法理解 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;
}
I can't understand where the final
keyword is really handy when it is used on method parameters.
If we exclude the usage of anonymous classes, readability and intent declaration then it seems almost worthless to me.
Enforcing that some data remains constant is not as strong as it seems.
If the parameter is a primitive then it will have no effect since the parameter is passed to the method as a value and changing it will have no effect outside the scope.
If we are passing a parameter by reference, then the reference itself is a local variable and if the reference is changed from within the method, that would not have any effect from outside of the method scope.
Consider the simple test example below.
This test passes although the method changed the value of the reference given to it, it has no effect.
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;
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(12)
停止变量的重新分配
虽然这些答案在智力上很有趣,但我还没有阅读简短的简单答案:
无论变量是静态变量、成员变量、局部变量还是自变量/参数变量,效果都是完全相同的。
示例
让我们看看实际效果。
考虑这个简单的方法,其中两个变量(arg 和 x)都可以重新分配不同的对象。
将局部变量标记为final。 这会导致编译器错误。
相反,让我们将参数变量标记为final。 这也会导致编译器错误。
故事的道德启示:
永远不要重新分配参数
作为良好的编程习惯(在任何语言中),您应该永远将参数/参数变量重新分配给除调用方法传递的对象之外的对象。 在上面的示例中,永远不应该写行
arg =
。 既然人类都会犯错误,而程序员也是人,那么我们就请编译器来帮助我们吧。 将每个参数/参数变量标记为“最终”,以便编译器可以找到并标记任何此类重新分配。回想起来
,正如其他答案中指出的那样……
鉴于 Java 最初的设计目标是帮助程序员避免读取超出数组末尾的愚蠢错误,Java 应该被设计为自动强制所有参数/参数变量为“final”。 换句话说,参数不应该是变量。 但事后看来,这是 20/20 的愿景,Java 设计者当时正忙得不可开交。
那么,总是在所有参数中添加
final
吗?我们是否应该将
final
添加到声明的每个方法参数中?➥ 仅当方法的代码很长或很复杂时才添加
final
,此时参数可能会被误认为是局部变量或成员变量,并且可能会被重新分配。如果您坚持从不重新分配参数的做法,您将倾向于为每个参数添加一个
final
。 但这很乏味,并且使声明有点难以阅读。对于简短的简单代码,其中参数显然是一个参数,而不是局部变量或成员变量,我不费心添加
final
。 如果代码非常明显,我或任何其他程序员都不可能在进行维护或重构时意外地将参数变量误认为是参数以外的东西,那么就不必费心了。 在我自己的工作中,我仅在较长或涉及较多的代码中添加final
,其中参数可能会被误认为是本地变量或成员变量。#为完整性而添加的另一个案例
记录
Java 16 带来了新的记录特征。 记录是定义类的一种非常简短的方式,其中心目的只是不可变且透明地携带数据。
您只需声明类名及其成员字段的名称和类型。 编译器隐式提供构造函数、getters、
equals
&hashCode
和toString
。这些字段是只读的,没有设置器。 因此,
记录
是一种无需将参数标记为final
的情况。 它们实际上已经是最终版本。 事实上,编译器在声明记录字段时禁止使用final
。Stop a Variable’s Reassignment
While these answers are intellectually interesting, I've not read the short simple answer:
Whether the variable is a static variable, member variable, local variable, or argument/parameter variable, the effect is entirely the same.
Example
Let’s see the effect in action.
Consider this simple method, where the two variables (arg and x) can both be re-assigned different objects.
Mark the local variable as final. This results in a compiler error.
Instead, let’s mark the parameter variable as final. This too results in a compiler error.
Moral of the story:
Never Reassign Arguments
As good programming practice (in any language), you should never re-assign a parameter/argument variable to an object other than the object passed by the calling method. In the examples above, one should never write the line
arg =
. Since humans make mistakes, and programmers are human, let’s ask the compiler to assist us. Mark every parameter/argument variable as 'final' so that the compiler may find and flag any such re-assignments.In Retrospect
As noted in other answers…
Given Java's original design goal of helping programmers to avoid dumb mistakes such as reading past the end of an array, Java should have been designed to automatically enforce all parameter/argument variables as 'final'. In other words, Arguments should not be variables. But hindsight is 20/20 vision, and the Java designers had their hands full at the time.
So, always add
final
to all arguments?Should we add
final
to each and every method parameter being declared?➥ Add
final
only when the method’s code is long or complicated, where the argument may be mistaken for a local or member variable and possibly re-assigned.If you buy into the practice of never re-assigning an argument, you will be inclined to add a
final
to each. But this is tedious and makes the declaration a bit harder to read.For short simple code where the argument is obviously an argument, and not a local variable nor a member variable, I do not bother adding the
final
. If the code is quite obvious, with no chance of me nor any other programmer doing maintenance or refactoring accidentally mistaking the argument variable as something other than an argument, then don’t bother. In my own work, I addfinal
only in longer or more involved code where an argument might be mistaken for a local or member variable.#Another case added for the completeness
record
Java 16 brings the new records feature. A record is a very brief way to define a class whose central purpose is to merely carry data, immutably and transparently.
You simply declare the class name along with the names and types of its member fields. The compiler implicitly provides the constructor, getters,
equals
&hashCode
, andtoString
.The fields are read-only, with no setters. So a
record
is one case where there is no need to mark the argumentsfinal
. They are already effectively final. Indeed, the compiler forbids usingfinal
when declaring the fields of a record.If you provide an optional constructor, there you can mark
final
.有时,明确(为了可读性)变量不会改变是件好事。 下面是一个简单的示例,其中使用
final
可以避免一些可能出现的麻烦:如果您忘记了 setter 上的“this”关键字,那么您想要设置的变量就不会被设置。 但是,如果您在参数上使用
final
关键字,则该错误将在编译时被捕获。Sometimes it's nice to be explicit (for readability) that the variable doesn't change. Here's a simple example where using
final
can save some possible headaches:If you forget the 'this' keyword on a setter, then the variable you want to set doesn't get set. However, if you used the
final
keyword on the parameter, then the bug would be caught at compile time.是的,排除匿名类、可读性和意图声明,它几乎毫无价值。 难道这三样东西就没有价值了吗?
就我个人而言,我倾向于不使用
final
作为局部变量和参数,除非我在匿名内部类中使用该变量,但我当然可以理解那些想要明确参数的人的观点值本身不会改变(即使它引用的对象改变了它的内容)。 对于那些发现这增加了可读性的人来说,我认为这是一件完全合理的事情。如果有人确实声称它确实以某种方式保持数据恒定,而事实并非如此,那么您的观点会更重要 - 但我不记得看到过任何此类声明。 您是否认为有很多开发人员认为
final
的效果比实际效果更大?编辑:我真的应该用 Monty Python 参考文献来总结所有这些; 这个问题似乎有点类似于问“罗马人为我们做过什么?”
Yes, excluding anonymous classes, readability and intent declaration it's almost worthless. Are those three things worthless though?
Personally I tend not to use
final
for local variables and parameters unless I'm using the variable in an anonymous inner class, but I can certainly see the point of those who want to make it clear that the parameter value itself won't change (even if the object it refers to changes its contents). For those who find that adds to readability, I think it's an entirely reasonable thing to do.Your point would be more important if anyone were actually claiming that it did keep data constant in a way that it doesn't - but I can't remember seeing any such claims. Are you suggesting there's a significant body of developers suggesting that
final
has more effect than it really does?EDIT: I should really have summed all of this up with a Monty Python reference; the question seems somewhat similar to asking "What have the Romans ever done for us?"
让我解释一下你必须使用final的一种情况,Jon已经提到过:
如果你在方法中创建一个匿名内部类并使用局部变量(例如方法参数)在该类中,编译器会强制您将参数设置为最终参数:
这里
from
和to
参数需要是最终参数,以便可以在匿名类中使用它们。该要求的原因是:局部变量存在于堆栈中,因此它们仅在方法执行时存在。 但是,匿名类实例是从该方法返回的,因此它的寿命可能会更长。 您无法保留堆栈,因为后续方法调用需要它。
因此,Java 所做的就是将这些局部变量的副本作为隐藏实例变量放入匿名类中(如果检查字节码,就可以看到它们)。 但如果它们不是最终的,人们可能会期望匿名类和方法看到另一个对变量所做的更改。 为了维持只有一个变量而不是两个副本的错觉,它必须是最终的。
Let me explain a bit about the one case where you have to use final, which Jon already mentioned:
If you create an anonymous inner class in your method and use a local variable (such as a method parameter) inside that class, then the compiler forces you to make the parameter final:
Here the
from
andto
parameters need to be final so they can be used inside the anonymous class.The reason for that requirement is this: Local variables live on the stack, therefore they exist only while the method is executed. However, the anonymous class instance is returned from the method, so it may live for much longer. You can't preserve the stack, because it is needed for subsequent method calls.
So what Java does instead is to put copies of those local variables as hidden instance variables into the anonymous class (you can see them if you examine the byte code). But if they were not final, one might expect the anonymous class and the method seeing changes the other one makes to the variable. In order to maintain the illusion that there is only one variable rather than two copies, it has to be final.
我一直在参数上使用final。
加了这么多吗? 并不真地。
我会把它关掉吗? 不。
原因是:我发现了 3 个错误,人们编写了草率的代码,并且未能在访问器中设置成员变量。 事实证明,所有错误都很难找到。
我希望看到这在 Java 的未来版本中成为默认设置。 按值/引用传递的事情让很多初级程序员感到困惑。
另一件事......我的方法往往具有较少的参数,因此方法声明上的额外文本不是问题。
I use final all the time on parameters.
Does it add that much? Not really.
Would I turn it off? No.
The reason: I found 3 bugs where people had written sloppy code and failed to set a member variable in accessors. All bugs proved difficult to find.
I'd like to see this made the default in a future version of Java. The pass by value/reference thing trips up an awful lot of junior programmers.
One more thing.. my methods tend to have a low number of parameters so the extra text on a method declaration isn't an issue.
在方法参数中使用 Final 与调用方参数发生的情况无关。 它只是为了将其标记为在该方法内不发生更改。 当我尝试采用更函数式的编程风格时,我看到了其中的价值。
Using final in a method parameter has nothing to do with what happens to the argument on the caller side. It is only meant to mark it as not changing inside that method. As I try to adopt a more functional programming style, I kind of see the value in that.
就我个人而言,我不会在方法参数上使用final,因为它会给参数列表带来太多混乱。
我更喜欢强制方法参数不会通过 Checkstyle 等方式更改。
对于局部变量,我尽可能使用 Final,我什至让 Eclipse 在我的个人项目设置中自动执行此操作。
我当然想要一些更强大的东西,比如 C/C++ const。
Personally I don't use final on method parameters, because it adds too much clutter to parameter lists.
I prefer to enforce that method parameters are not changed through something like Checkstyle.
For local variables I use final whenever possible, I even let Eclipse do that automatically in my setup for personal projects.
I would certainly like something stronger like C/C++ const.
简短的回答:
final
有一点帮助,但是......在客户端使用防御性编程。事实上,
final
的问题在于它只强制引用 保持不变,欣喜地允许引用的对象成员在调用者不知情的情况下发生变异。 因此,这方面的最佳实践是在调用方进行防御性编程,创建深度不可变的实例或对象的深层副本,这些实例或对象有被不道德的 API 窃取的危险。Short answer:
final
helps a tiny bit but... use defensive programming on the client side instead.Indeed, the problem with
final
is that it only enforces the reference is unchanged, gleefully allowing the referenced object members to be mutated, unbeknownst to the caller. Hence the best practice in this regard is defensive programming on the caller side, creating deeply immutable instances or deep copies of objects that are in danger of being mugged by unscrupulous APIs.由于 Java 传递参数的副本,我觉得
final
的相关性相当有限。 我猜这个习惯来自 C++ 时代,你可以通过执行const char const *
来禁止引用内容被更改。 我觉得这种东西会让你相信开发者本质上是愚蠢的,并且需要受到保护以防止他输入的每个字符。 谦虚地说,即使我省略了final
(除非我不希望有人重写我的方法和类),我编写的错误也很少。 也许我只是一个老派的开发者。Since Java passes copies of arguments I feel the relevance of
final
is rather limited. I guess the habit comes from the C++ era where you could prohibit reference content from being changed by doing aconst char const *
. I feel this kind of stuff makes you believe the developer is inherently stupid as f*** and needs to be protected against truly every character he types. In all humbleness may I say, I write very few bugs even though I omitfinal
(unless I don't want someone to override my methods and classes). Maybe I'm just an old-school dev.我从不在参数列表中使用final,它只会像之前的受访者所说的那样增加混乱。 另外,在 Eclipse 中,您可以设置参数分配来生成错误,因此在参数列表中使用 Final 对我来说似乎相当多余。
有趣的是,当我启用 Eclipse 设置以进行参数分配时,会生成一个错误,捕获了这段代码(这只是我记忆流程的方式,而不是实际的代码。):-
唱反调,这样做究竟有什么问题?
I never use final in a parameter list, it just adds clutter like previous respondents have said. Also in Eclipse you can set parameter assignment to generate an error so using final in a parameter list seems pretty redundant to me.
Interestingly when I enabled the Eclipse setting for parameter assignment generating an error on it caught this code (this is just how I remember the flow, not the actual code. ) :-
Playing devil's advocate, what exactly is wrong with doing this?
将 Final 添加到参数声明的另一个原因是,它有助于识别需要重命名的变量,作为“提取方法”重构的一部分。 我发现在开始大型方法重构之前向每个参数添加 Final 可以快速告诉我在继续之前是否需要解决任何问题。
然而,我通常在重构结束时将它们删除为多余的。
One additional reason to add final to parameter declarations is that it helps to identify variables that need to be renamed as part of a "Extract Method" refactoring. I have found that adding final to each parameter prior to starting a large method refactoring quickly tells me if there are any issues I need to address before continuing.
However, I generally remove them as superfluous at the end of the refactoring.
跟进米歇尔的帖子。 我又给自己举了一个例子来解释。 我希望它能有所帮助。
从上面的代码来看,在方法thisIsWhy()中,我们实际上没有将[argument MyObj obj]分配给 MyParam 中的真实引用。 相反,我们只在 MyParam 内的方法中使用 [argument MyObj obj]。
但是在我们完成方法 thisIsWhy() 后,参数(对象)MyObj 还应该存在吗?
似乎应该存在,因为我们可以看到在main中我们仍然调用方法showObjName()并且它需要到达obj。 即使方法已经返回,MyParam 仍将使用/到达方法参数!
Java 真正实现这一点的方法是生成一个副本,也是 MyParam 对象内的 参数 MyObj obj 的隐藏引用(但它不是 MyParam 中的正式字段,因此我们看不到它
)我们调用“showObjName”,它将使用该引用来获取相应的值。
但是如果我们没有将参数放在final 中,就会导致我们可以将新的内存(对象)重新分配给参数MyObj obj。
从技术上讲,根本不存在冲突!如果允许我们这样做,则会出现以下情况:
没有冲突,但是“令人困惑!!”因为它们都使用相同的“参考名称”,即“obj”。
为了避免这种情况,请将其设置为“最终”,以避免程序员执行“容易出错”的代码。
Follow up by Michel's post. I made myself another example to explain it. I hope it could help.
From the code above, in the method thisIsWhy(), we actually didn't assign the [argument MyObj obj] to a real reference in MyParam. In instead, we just use the [argument MyObj obj] in the method inside MyParam.
But after we finish the method thisIsWhy(), should the argument(object) MyObj still exist?
Seems like it should, because we can see in main we still call the method showObjName() and it needs to reach obj. MyParam will still use/reaches the method argument even the method already returned!
How Java really achieve this is to generate a copy also is a hidden reference of the argument MyObj obj inside the MyParam object ( but it's not a formal field in MyParam so that we can't see it )
As we call "showObjName", it will use that reference to get the corresponding value.
But if we didn't put the argument final, which leads a situation we can reassign a new memory(object) to the argument MyObj obj.
Technically there's no clash at all! If we are allowed to do that, below will be the situation:
No clash, but "CONFUSING!!" Because they are all using the same "reference name" which is "obj".
To avoid this, set it as "final" to avoid programmer do the "mistake-prone" code.