为什么我在Java中的方法参数上要使用关键字"final"?

455

在方法参数上使用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;
}

118
关于术语的一个要点 - Java根本没有传递引用。它采用传递值的方式来传递引用,这并不是同一件事情。如果真正采用传递引用的语义,你的代码结果将会有所不同。 - Jon Skeet
10
传递引用和传值引用的区别是什么? - NobleUplift
1
在C语言的背景下更容易描述这种差异(至少对我来说是这样)。如果我传递一个指向方法的指针,如:<code>int foo(int bar)</code>,那么该指针会被按值传递。这意味着它被复制了,因此,如果我在该方法中执行类似于<code>free(bar); bar = malloc(...);</code>的操作,那么我刚刚做了一件很糟糕的事情。free调用实际上将释放指针所指向的内存块(因此传入的任何指针现在都是悬空的)。但是,<code>int foo(int &bar)</code>表示该代码是有效的,并且传入的指针的值将被更改。 - jerslan
4
第一个应该是int foo(int* bar),最后一个是int foo(int* &bar)。后者是通过引用传递指针,前者是通过值传递引用。 - jerslan
2
@Martin,我认为这是一个好的问题;请看问题的标题和帖子内容,以了解提出这个问题的原因。也许我在这里误解了规则,但这正是我搜索“方法中final参数的用途”时想要的问题。 - Victor Zamanian
标题是一个问题,但正文并不是。这是一个基于误解的例子。在另一个作用域中更改引用(通过方法调用)不会更改当前作用域中的引用。 - Martin
12个回答

0

在参数声明中添加final的另一个原因是它有助于识别需要重命名的变量,作为“提取方法”重构的一部分。我发现,在开始大型方法重构之前,将final添加到每个参数中可以快速告诉我是否有任何问题需要解决。

然而,通常在重构结束时我会将它们删除,因为它们是多余的。


-1

继Michel的帖子之后,我自己制作了另一个例子来解释它。希望能有所帮助。

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()中,我们实际上没有将[argument MyObj obj]分配给MyParam中的一个真实引用。相反,我们只是在MyParam内部的方法中使用[argument MyObj obj]
但是,在完成方法thisIsWhy()之后,参数(对象)MyObj是否仍然存在? 看起来应该存在,因为我们可以在主函数中看到我们仍然调用了方法showObjName(),并且它需要访问obj。即使方法已经返回,MyParam仍将使用/访问方法参数!
Java如何实现这一点是生成一个argument MyObj obj的副本,也是MyParam对象中的一个隐藏引用(但它不是MyParam中的正式字段,因此我们看不到它)。
当我们调用“showObjName”时,它将使用该引用获取相应的值。
但是,如果我们没有将参数设置为final,则会导致我们可以将新的内存(对象)重新分配给argument MyObj obj,从而产生一种情况。

从技术上讲,根本没有冲突! 如果我们被允许这样做,下面将是情况:

  1. 现在我们有一个隐藏的 [MyObj obj] 指向一个 [Memory A in heap],现在在 MyParam 对象中。
  2. 我们还有另一个 [MyObj obj],它是参数指向一个 [Memory B in heap],现在在 thisIsWhy 方法中。

没有冲突,但“令人困惑!” 因为它们都使用相同的“引用名称”,即“obj”

为了避免这种情况,将其设置为“final”以避免程序员编写“容易出错”的代码。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接