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

144

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

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

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

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

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

Какой «контракт» finalобещает метод?

Towi
источник

Ответы:

158

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

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

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

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

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

  • Надежность и Договор - Объекты состоят из примитивов ( 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
NawaMan
источник
27

Какой «контракт» обещает последний метод?

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

Josefx
источник
Но разве это представление не означает, что на мой исходный вопрос «этот последний метод всегда будет вести себя так, как было обещано», что я не должен вызывать какие-либо незавершенные методы изнутри последнего метода? Потому что, если я это сделаю, вызванный метод мог быть переопределен, и поэтому я не могу гарантировать поведение моего последнего метода?
Towi
8

Прежде всего, вы можете отмечать не абстрактные классы, 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);
  }
}  
Виктор Сорокин
источник
да я знаю про выпускные классы - легкий случай. Хороший момент по поводу полей final с JMM, синхронизация не требуется ... хм: это относится только к "указателю", верно? Я все еще мог изменить несинхронно объект, на который он ссылается (хорошо, не в String, а в пользовательских классах). Но то, что вы сказали о «final не гарантирует поведения», в точности мою. Согласен, важны документы и дизайн.
towi
@towi, вы правы, что finalне гарантирует видимости изменений, внесенных в составные объекты, такие как Map.
Виктор Сорокин
0

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

Джим Ферранс
источник
1
Да, это я имел в виду. Правильно. Мне нравится термин «слабая гарантия» :-) И мне (в основном) нравится C ++ const. Как в char const * const = "Hello"или char const * const addName(char const * const name) const...
Towi 05
0

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

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

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

Брайан Роуч
источник
1
Что ж, технически он будет делать то, что написал автор базового класса .
Joey