当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;
}

当前回答

让我来解释一下你必须使用final的一种情况,Jon已经提到过:

如果你在你的方法中创建了一个匿名的内部类,并且在这个类中使用了一个局部变量(比如一个方法参数),那么编译器会强制你将参数设为final:

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参数需要是final,这样它们才能在匿名类中使用。

这种需求的原因是:局部变量存在于堆栈中,因此它们只在方法执行时存在。但是,匿名类实例是从方法返回的,因此它可能存在更长的时间。不能保留堆栈,因为后续的方法调用需要它。

因此,Java所做的是将这些局部变量的副本作为隐藏的实例变量放入匿名类中(如果检查字节代码,您可以看到它们)。但如果它们不是最终的,人们可能会期望匿名类和方法看到另一个对变量所做的更改。为了保持只有一个变量而不是两个副本的错觉,它必须是最终的。

其他回答

是的,除去匿名类、可读性和意图声明,它几乎毫无价值。但这三样东西毫无价值吗?

就我个人而言,我倾向于不对局部变量和参数使用final,除非我在匿名内部类中使用变量,但我当然可以看到那些想要明确参数值本身不会改变的人的观点(即使它引用的对象改变了其内容)。对于那些发现这增加了可读性的人来说,我认为这是一件完全合理的事情。

如果有人真的声称它确实以某种方式保持数据不变,那么你的观点将更加重要——但我不记得看到过任何这样的说法。你的意思是有很多开发者认为final的效果比实际效果更大?

编辑:我真的应该用Monty Python的参考来总结所有这些;这个问题似乎有点类似于问“罗马人为我们做过什么?”

跟进米歇尔的帖子。我给自己举了另一个例子来解释。我希望它能有所帮助。

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()方法中,我们实际上并没有将[参数MyObj obj]分配给MyParam中的一个实际引用。相反,我们只是在MyParam的方法中使用[参数MyObj obj]。

但是在我们完成方法thisIsWhy()后,参数(对象)MyObj是否仍然存在?

似乎它应该,因为我们可以看到在main中我们仍然调用方法showObjName(),它需要到达obj。即使方法已经返回,MyParam仍然会使用/到达方法参数!

Java真正实现这一点的方法是在MyParam对象中生成MyObj参数的一个隐藏引用(但它不是MyParam中的一个正式字段,所以我们看不到它)

当我们调用“showObjName”时,它将使用该引用来获得相应的值。

但如果我们没有将参数设为final,这将导致一种情况,我们可以将一个新的内存(对象)重新分配给参数MyObj obj。

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

我们现在在MyParam对象中有一个隐藏的[MyObj obj]指向一个[Memory a in heap]。 我们还有另一个[MyObj obj],它是指向[堆中的内存B]的参数,现在存在于thisIsWhy方法中。

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

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

简短的回答:最终有一点帮助,但是……在客户端使用防御性编程。

实际上,final的问题在于它只强制引用不变,允许被引用的对象成员发生变化,而调用者不知道。因此,在这方面的最佳实践是在调用方进行防御性编程,创建深度不可变的实例或对象的深度副本,这些实例或对象有被肆无忌惮的api窃取的危险。

在方法形参中使用final与调用方的实参发生了什么无关。它只是为了将其标记为在该方法中没有更改。当我尝试采用更函数式的编程风格时,我看到了其中的价值。

让我来解释一下你必须使用final的一种情况,Jon已经提到过:

如果你在你的方法中创建了一个匿名的内部类,并且在这个类中使用了一个局部变量(比如一个方法参数),那么编译器会强制你将参数设为final:

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参数需要是final,这样它们才能在匿名类中使用。

这种需求的原因是:局部变量存在于堆栈中,因此它们只在方法执行时存在。但是,匿名类实例是从方法返回的,因此它可能存在更长的时间。不能保留堆栈,因为后续的方法调用需要它。

因此,Java所做的是将这些局部变量的副本作为隐藏的实例变量放入匿名类中(如果检查字节代码,您可以看到它们)。但如果它们不是最终的,人们可能会期望匿名类和方法看到另一个对变量所做的更改。为了保持只有一个变量而不是两个副本的错觉,它必须是最终的。