Java的`final`方法:它承诺了什么?

152

在Java类中,可以定义一个方法为final,以标记该方法不可被覆盖:

public class Thingy {
    public Thingy() { ... }
    public int operationA() {...}
    /** this method does @return That and is final. */
    public final int getThat() { ...}
}

我理解,定义一个方法为final,类的设计者承诺这个方法将永远按照描述或意图运行,这可能对于防止意外覆盖或提高性能有一定作用。但这不是我的问题。

我的问题是:从面向对象编程(OOP)的角度来看,如果该方法所做的事情比仅仅提供属性更加复杂,则往往超出了类作者的影响范围。

我清楚地了解到语法限制,但在OOP方面,这意味着什么?大多数类作者是否正确地使用final?

final方法承诺了什么样的“契约”?

5个回答

164

正如所提到的,final 用于 Java 方法以标记该方法无法被覆盖(对于对象范围)或隐藏(对于静态)。这允许原始开发人员创建功能,不能被子类更改,并且这就是它提供的全部保证。

这意味着如果方法依赖于其他可定制组件,如非公共字段/方法,则最终方法的功能仍然可以定制。尽管如此,这也是好的,因为(通过多态性)它允许部分定制。

禁止某些内容进行定制有许多原因,包括:

  • 性能 -- 一些编译器可以分析和优化操作,特别是没有副作用的操作。

  • 获取封装数据 -- 查看不可变对象,其中它们的属性在构建时设置并且永远不应更改。或者是从这些属性派生出的计算值。Java 的 String 类就是很好的例子。

  • 可靠性和约定 -- 对象由原始类型 (int, char, double 等) 和/或其他对象组成。当它们用于更大的对象时,并非所有适用于这些组件的操作都应该适用或甚至是逻辑的。使用 final 修饰符的方法可以确保这一点。计数器类就是一个很好的例子。


public class Counter {
    private int counter = 0;

    public final int count() {
        return counter++;
    }

    public final int reset() {
        return (counter = 0);
    }
}

如果public final int count()方法不是final的,我们可以这样做:

Counter c = new Counter() {   
    public int count() {
        super.count();   
        return super.count();   
    } 
}

c.count(); // now count 2

或者像这样:

Counter c = new Counter() {
    public int count() {
        int lastCount = 0;
        for (int i = super.count(); --i >= 0; ) {
            lastCount = super.count();
        }

        return lastCount;
    }
}

c.count(); // Now double count

29

一个final方法承诺了什么样的“合约”?

从另一个角度看,任何非final方法都会做出隐式保证,即你可以用自己的实现覆盖它,并且类仍将按预期工作。当你无法保证你的类支持重写方法时,应该将其设置为final。


但是,这个观点是否不暗示着对于我的原始问题“这个最终方法将始终按照承诺的方式运行”,我不应该从最终方法内部调用任何非最终方法?因为如果我这样做,被调用的方法可能已经被覆盖,因此我无法保证我的最终方法的行为? - towi

8
首先,您可以将非抽象类、字段和方法标记为final。这样整个类就不能被子类化了。因此,类的行为将固定不变。
我同意,如果这些方法调用了非final方法,则标记方法final不能保证它们在子类中的行为相同。如果确实需要固定行为,必须通过约定和谨慎的设计来实现。并且不要忘记在JavaDoc(Java文档)中注明!
最后但同样重要的是,在Java内存模型(JMM)中,final关键字具有非常重要的作用。 JMM保证为了实现对final字段的可见性,您不需要适当的同步。例如:
class A implements Runnable {
  final String caption = "Some caption";                           

  void run() {
    // no need to synchronize here to see proper value of final field..
    System.out.println(caption);
  }
}  

是的,我知道final类——很容易理解。你提到的关于JMM的final字段也很有道理,不需要同步...嗯:这只涉及“指针”,对吧?我仍然可以修改它所引用的对象(在用户定义的类中,不适用于String)。但你所说的“final不能保证行为”的确是我的观点。我同意,文档和设计非常重要。 - towi
@towi 你是对的,final 不能确保对于复合对象(例如 Map)所做的更改可见性。 - Victor Sorokin

0

1
从技术上讲,它将执行基类作者编写的内容 - Joey

0

我不确定你能否对使用"final"的影响以及它如何影响软件的整体设计契约做出任何断言。你可以保证没有开发人员可以覆盖这个方法并以此方式使其契约无效。但另一方面,最终方法可能依赖于由子类设置值的类或实例变量,并且可以调用其他被覆盖的类方法。因此,最终方法最多只是一个非常弱的保证。


1
是的,那就是我想表达的意思。没错。我喜欢“弱保证”这个术语 :-) 而且我(大多数时候)也喜欢 C++ 的 const。比如 char const * const = "Hello" 或者 char const * const addName(char const * const name) const... - towi

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