【问题标题】:Java `final` method: what does it promise?Java `final` 方法:它承诺什么?
【发布时间】:2011-07-29 16:38:09
【问题描述】:

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

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

这很清楚,它可能对防止意外覆盖或性能有所帮助——但这不是我的问题。

我的问题是:从 OOP 的角度来看,我理解,通过定义一个方法 final 类设计器promises,该方法将始终如所描述或暗示的那样工作。但通常这可能不受类作者的影响,如果方法正在做的事情更复杂,那么只需提供一个属性

句法约束对我来说很清楚,但是在 OOP 意义上的含义是什么?大多数班级作者在这个意义上正确使用了final吗?

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

【问题讨论】:

    标签: java oop final


    【解决方案1】:

    首先,你可以标记非抽象类final以及字段和方法。这样整个类就不能被子类化。因此,类的行为将被修复。

    我同意标记方法final 不保证如果这些方法调用非最终方法,它们在子类中的行为将相同。如果确实需要修复行为,则必须通过约定和精心设计来实现。并且不要忘记在 javadoc 中注意这一点!(java 文档)

    最后但并非最不重要的一点是,final 关键字在 Java 内存模型 (JMM) 中具有非常重要的作用。 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);
      }
    }  
    

    【讨论】:

    • 是的,我知道最终课程——简单的案例。关于 JMM 的最终字段的好点,不需要同步......嗯:这只是指“指针”,对吗?我仍然可以不同步地修改它引用的对象(好吧,不是在String 中,而是在用户定义的类中)。但是你所说的“最终并不能保证行为”是完全我的观点。我同意,文档和设计很重要。
    • @towi 你说得对,final 不会确保对复合对象所做的更改可见,例如 Map
    【解决方案2】:

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

    这意味着,如果该方法依赖于其他可定制的组件,例如非公共字段/方法,那么最终方法的功能可能仍然是可定制的。这很好,因为(使用多态性)它允许部分自定义。

    阻止某些内容可定制的原因有很多,包括:

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

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

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


    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
    

    【讨论】:

      【解决方案3】:

      不,它不受课程作者的影响。您不能在派生类中覆盖它,因此它将执行基类作者的意图。

      http://download.oracle.com/javase/tutorial/java/IandI/final.html

      值得注意的是它建议从构造函数调用的方法应该是final

      【讨论】:

      • 好吧,从技术上讲,它会按照基类作者写的
      【解决方案4】:

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

      换个角度看,任何非最终方法都隐式保证您可以用自己的实现覆盖它,并且该类仍将按预期工作。当你不能保证你的类支持覆盖一个方法时,你应该让它成为最终的。

      【讨论】:

      • 但是这种观点是否不暗示我原来的问题“这个最终方法将始终如承诺的那样”我不能从最终方法内部调用任何非最终方法?因为,如果我这样做,调用的方法可能已被覆盖,因此我不能保证我的最终方法的行为?
      【解决方案5】:

      我不确定您能否就“final”的使用以及它如何影响软件的整体设计合同做出任何断言。您可以保证没有开发人员可以覆盖此方法并以这种方式使其合同无效。但另一方面,final 方法可能依赖于其值由子类设置的类或实例变量,并且可以调用 覆盖的其他类方法。所以 final 最多是一个很弱的保证。

      【讨论】:

      • 是的,这就是我的意思。正确的。我喜欢“弱保证”这个词 :-) 而且我(大部分)喜欢 C++ 的const。如char const * const = "Hello"char const * const addName(char const * const name) const...
      猜你喜欢
      • 2020-04-27
      • 1970-01-01
      • 2016-05-29
      • 2017-10-09
      • 2015-06-05
      • 1970-01-01
      • 2017-06-01
      • 1970-01-01
      • 2021-05-17
      相关资源
      最近更新 更多