Java `final` метод: что он обещает?

В классе Java метод может быть определен как final, чтобы отметить, что этот метод не может быть переопределен:

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

Это ясно, и это может быть полезно для защиты от случайного переопределения или, может быть, производительности; но это не мой вопрос.

Мой вопрос: с точки зрения ООП я понял, что, определяя метод final конструктор классов promises, этот метод всегда будет работать так, как описано или подразумевается. Но часто это может быть вне влияния автора класса, если то, что делает этот метод, более сложно, чем просто передать свойство.

Синтаксическое ограничение мне ясно, но каково значение смысла ООП? Является ли final правильным в этом смысле большинством авторов классов?

Какой "контракт" выполняет метод final?

Ответ 1

Как уже упоминалось, final используется с Java-методом, чтобы отметить, что метод не может быть переопределен (для области объекта) или скрыт (для статического). Это позволяет оригинальному разработчику создавать функциональные возможности, которые не могут быть изменены подклассами, и это все гарантии, которые он предоставляет.

Это означает, что если этот метод зависит от других настраиваемых компонентов, таких как непубличные поля/методы, функциональность конечного метода может по-прежнему настраиваться. Это хорошо, хотя (с полиморфизмом) он позволяет частичную настройку.

Существует ряд причин, по которым нельзя что-то настраивать, в том числе:

  • Производительность. Некоторые компиляторы могут анализировать и оптимизировать работу, особенно без побочных эффектов.

  • Получить инкапсулированные данные - посмотреть на неизменяемые объекты, где их атрибуты заданы во время построения и никогда не должны изменяться. Или вычисленное значение, полученное из этих атрибутов. Хорошим примером является класс Java String.

  • Надежность и контракт. Объекты состоят из примитивов (int, char, double и т.д.) и/или других объектов. Не все операции, применимые к этим компонентам, должны быть применимы или даже логичны, когда они используются в более крупном объекте. Для этого можно использовать методы с модификатором 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

Ответ 2

Какой "контракт" дает окончательный метод?

Посмотрите на это по-другому, любой не окончательный метод делает скрытую гарантию того, что вы можете переопределить его своей собственной реализацией, и класс будет работать, как ожидалось. Если вы не можете гарантировать, что ваш класс поддерживает переписывание метода, вы должны сделать его окончательным.

Ответ 3

Прежде всего, вы можете пометить не-абстрактные классы final, а также поля и методы. Таким образом, весь класс нельзя подклассифицировать. Таким образом, поведение класса будет исправлено.

Я согласен с тем, что методы маркировки final не гарантируют, что их поведение будет одинаковым в подклассах, если эти методы вызывают не конечные методы. Если необходимо действительно исправлять поведение, это должно быть достигнуто путем условного и тщательного проектирования. И не забывайте об этом в javadoc! (Java documentation)

Последнее, но не менее важное, ключевое слово 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);
  }
}  

Ответ 4

Нет, это не вне влияния автора класса. Вы не можете переопределить его в производном классе, поэтому он будет делать то, что предполагал автор базового класса.

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

Стоит отметить, что это означает, что методы, вызываемые из конструкторов, должны быть final.

Ответ 5

Я не уверен, что вы можете делать какие-либо утверждения об использовании "final" и как это влияет на общий контракт на разработку программного обеспечения. Вы гарантированно, что ни один разработчик не сможет переопределить этот метод и таким образом аннулировать его контракт. Но, с другой стороны, конечный метод может полагаться на переменные класса или экземпляра, значения которых задаются подклассами и могут вызывать другие методы класса, которые переопределены. Таким образом, окончательный результат - это очень слабая гарантия.