为什么我应该在Java的方法参数中使用关键字“final”?
当在方法参数上使用final
关键字时,我无法理解final
关键字在哪里。
如果我们排除匿名类的使用,可读性和意向声明,那么对我来说似乎几乎是毫无价值的。
强调一些数据保持不变并不像看起来那么强劲。
-
如果参数是一个基元,那么它将不起作用,因为该参数作为一个值传递给该方法,并且改变它将不会影响该范围。
-
如果我们通过引用传递一个参数,那么引用本身就是一个局部variables,如果引用是从方法内部改变的,那么在方法范围之外不会有任何影响。
考虑下面的简单testing例子。 虽然该方法改变了给定的参考值,但该testing通过,但是没有效果。
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; }
有时为了变得清晰(为了可读性),variables不会改变。 这是一个简单的例子,使用final可以节省一些可能的麻烦
public void setTest(String test) { test = test; }
如果您忘记了setter上的“this”关键字,那么您要设置的variables不会被设置。 但是,如果你在参数上使用了final关键字,那么bug在编译时会被捕获。
停止variables的重新分配
虽然这些答案在智力上是有趣的,但是我还没有阅读简单的答案:
如果希望编译器防止将variables重新分配给其他对象,请使用关键字final 。
无论variables是成员variables,局部variables还是参数(参数)variables,效果都完全相同。
例
让我们看看行动中的效果。
考虑这个简单的方法,其中两个variables( 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. }
将局部variables标记为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"; }
相反,让我们将参数variables标记为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. }
故事的道德启示:
如果你想确保一个variables总是指向同一个对象,那么标记variablesfinal 。
永不重新分配参数
作为一种良好的编程习惯(用任何语言),你永远不应该为调用方法传递的对象以外的对象重新分配一个参数variables。 在上面的例子中,不应该写行arg =
。 既然人类犯了错误,而程序员是人类,让我们让编译器来帮助我们。 将每个参数/参数variables标记为“final”,以便编译器可以find并标记任何这样的重新分配。
回想起来
正如其他答案中所指出的那样…考虑到Java的原始devise目标是帮助程序员避免愚蠢的错误,比如读取数组的末尾,Java应该被devise为自动将所有参数/参数variables强制为“final”。 换句话说, 参数不应该是variables 。 但后见之明是20/20的愿景,而Javadevise师当时已经手足无措了。
为了完整性增加了另一个案例
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 }
是的,除了匿名类,可读性和意向声明,它几乎是毫无价值的。 这三件事情是不值钱的吗?
我个人倾向于不使用final
作为局部variables和参数,除非我在匿名内部类中使用variables,但我可以肯定地看到那些想清楚表明参数值本身不会改变的人即使它引用的对象改变其内容)。 对于那些发现增加可读性的人,我认为这是一个完全合理的做法。
如果有人真的声称它确实保持了数据不变的方式,那么你的观点就更重要了,但我不记得看到这样的说法了。 你是否build议有一大批开发者认为final
比实际效果更强?
编辑:我真的应该用Monty Python的参考来总结这一切。 这个问题似乎有点类似于“罗马人为我们做了什么?
让我来解释一下你必须使用最后一个例子,Jon已经提到过了:
如果在方法中创build一个匿名的内部类,并在该类中使用局部variables(例如方法参数),那么编译器将强制您将该参数设置为最终的:
public Iterator<Integer> createIntegerIterator(final int from, final int to) { return new Iterator<Integer>(){ int index = from; public Integer next() { return index++; } public boolean hasNext() { return index <= to; } // remove method omitted }; }
这里from
和to
参数需要是最终的,所以它们可以在匿名类中使用。
这个要求的原因是这样的:局部variables存在于堆栈中,因此只有在执行方法时才存在。 但是,匿名类实例是从方法返回的,所以它可能活得更久。 您无法保留堆栈,因为后续的方法调用需要它。
所以Java做的是将这些局部variables的副本作为隐藏的实例variables放到匿名类中(如果你检查字节码,你可以看到它们)。 但是,如果它们不是最终的,那么可能期望匿名类和方法会看到另一个对variables所做的更改。 为了保持只有一个variables而不是两个副本的错觉,它必须是最终的。
我一直在参数上使用final。
它增加了多less? 不是真的。
我会关掉它吗? 没有。
原因是:我发现3个错误,其中有人写了潦草的代码,并没有在访问器中设置成员variables。 所有的错误都很难find。
我希望看到这在未来的Java版本中成为默认值。 通过价值/参考的东西绊倒了很多初级程序员。
还有一件事..我的方法往往有less量的参数,所以额外的方法声明文本不是一个问题。
在方法参数中使用final与调用方的参数无关。 这只是为了标记它没有改变的方法。 当我尝试采用更多function的编程风格时,我看到其中的价值。
就个人而言,我不会在方法参数上使用final,因为它会给参数列表添加太多混乱。 我宁愿强制执行该方法参数不会通过类似Checkstyle的更改。
对于尽可能使用final的局部variables,我甚至让Eclipse在我的个人项目设置中自动执行。
我肯定会喜欢像C / C ++ const那样强大的东西。
由于Java传递了参数的副本,我觉得它的相关性是相当有限的。 我想这是来自C ++时代,你可以通过做一个const char const *
来禁止参考内容的改变。 我觉得这种东西让你相信开发者本身就是愚蠢的,并且需要被保护,以防止他所有的angular色都被他所感染。 在所有的谦虚,我必须说,我写了很less的错误,即使我承认除非我不希望有人重写我的方法和东西…也许我只是一个老学校的开发…: Ø
简短的回答: final
有一点帮助,但是…在客户端使用防御性编程。
事实上, final
的问题是它只强制引用不变,愉快地允许引用的对象成员变异,调用者不知道。 因此,在这方面最好的做法是在来电方进行防御性编程,创build深不可变的实例或深层拷贝的对象,这些对象有可能被不道德的API抢劫。
添加final参数声明的另外一个原因是,它有助于识别需要重新命名的variables,作为“Extract Method”重构的一部分。 我发现在开始一个大的方法重构之前给每个参数添加final可以快速地告诉我在继续之前是否有任何问题需要解决。
但是,我通常在重构结束时删除它们是多余的。
我从来没有在参数列表中使用final,只是像以前的受访者说的那样增加了混乱。 另外在Eclipse中,你可以设置参数赋值来产生一个错误,所以在参数列表中使用final对我来说似乎是相当多余的。 有趣的是,当我启用Eclipse设置的参数赋值生成一个错误,它抓住了这段代码(这只是我记得的stream程,而不是实际的代码): –
private String getString(String A, int i, String B, String C) { if (i > 0) A += B; if (i > 100) A += C; return A; }
扮演恶魔的拥护者,这到底是什么错误?
跟随米歇尔的post。 我自己也是另一个例子来解释它。 我希望它可以帮助。
public static void main(String[] args){ MyParam myParam = thisIsWhy(new MyObj()); myParam.setArgNewName(); System.out.println(myParam.showObjName()); } public static MyParam thisIsWhy(final MyObj obj){ MyParam myParam = new MyParam() { @Override public void setArgNewName() { obj.name = "afterSet"; } @Override public String showObjName(){ return obj.name; } }; return myParam; } public static class MyObj{ String name = "beforeSet"; public MyObj() { } } public abstract static class MyParam{ public abstract void setArgNewName(); public abstract String showObjName(); }
从上面的代码中,在方法thisIsWhy()中 ,我们实际上并没有将 MyObject [MyObj obj] 赋值给 MyParam中的真实引用 。 相反,我们只是在MyParam中的方法中使用[argument MyObj obj] 。
但是在完成方法thisIsWhy()之后 , 参数(object)MyObj是否仍然存在呢?
似乎应该,因为我们可以看到,我们仍然调用方法showObjName() ,它需要达到OBJ 。 即使方法已经返回,MyParam仍将使用/达到方法参数!
Java如何真正实现这一点,就是生成一个副本,也是MyParam对象内部参数MyObj obj的隐藏引用(但它不是MyParam中的一个正式字段,所以我们看不到它)
当我们调用“showObjName”时,它将使用该引用来获取相应的值。
但是如果我们没有把最终的参数放到最后,这会导致一个情况,我们可以重新分配一个新的内存(对象)给参数MyObj obj 。
从技术上讲,没有冲突! 如果我们被允许这样做,以下将是情况:
- 我们现在有一个隐藏的[MyObj obj]指向现在在MyParam对象中的[Memory in a heap]。
- 我们还有另一个[MyObj obj],它是现在生活在thisIsWhy方法中[现在堆中的内存B]的参数。
没有冲突,但“混乱!!” 因为它们全部使用了“obj”相同的“参考名称” 。
为了避免这种情况,请将其设置为“最终”,以避免程序员执行“容易出错”的代码。