Ключевое слово метода Java "final" и его использование - PullRequest
5 голосов
/ 15 января 2011

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

interface Garble {
  int zork();
}

interface Gnarf extends Garble {
  /**
   * This is the same as calling {@link #zblah(0)}
   */
  int zblah();
  int zblah(int defaultZblah);
}

А затем

abstract class AbstractGarble implements Garble {
  @Override
  public final int zork() { ... }
}

abstract class AbstractGnarf extends AbstractGarble implements Gnarf {
  // Here I absolutely want to fix the default behaviour of zblah
  // No Gnarf shouldn't be allowed to set 1 as the default, for instance
  @Override
  public final int zblah() { 
    return zblah(0);
  }

  // This method is not implemented here, but in a subclass
  @Override
  public abstract int zblah(int defaultZblah);
}

Я делаю это по нескольким причинам:

  1. Это помогает мне разработать иерархию типов.Когда я добавляю класс в иерархию, очень ясно, какие методы я должен реализовать, и какие методы я не могу переопределить (в случае, если я забыл подробности об иерархии)* бетон материал плохой в соответствии с принципами и схемами проектирования, такими как template method.Я не хочу, чтобы другие разработчики или мои пользователи делали это.

Так что ключевое слово final отлично работает для меня.Мой вопрос:

Почему он так редко используется в дикой природе?Можете ли вы показать мне несколько примеров / причин, по которым final (в аналогичном случае с моим) было бы очень плохо?

Ответы [ 5 ]

4 голосов
/ 15 января 2011

Почему он так редко используется в дикой природе?

Потому что вам нужно написать еще одно слово, чтобы сделать переменную / метод окончательным

Можете ли вы показать мне некоторые примеры / причины, по которым final (в аналогичном случае с моим) будет очень плохим?

Обычно я вижу такие примеры в библиотеках трехмерных деталей. В некоторых случаях я хочу расширить класс и изменить поведение. Особенно это опасно в библиотеках без открытого исходного кода без разделения интерфейса / реализации.

3 голосов
/ 15 января 2011

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

Но когда вы не ожидаете, что класс будет так или иначе расширен, почему суета?Конечно, если вы пишете библиотеку для кого-то другого, вы пытаетесь защитить ее настолько, насколько это возможно, но когда вы пишете «код конечного пользователя», есть момент, когда попытка сделать ваш код надежным будет только служитьРаздражать разработчиков, которые будут пытаться понять, как обходить лабиринт, который вы построили.Хотя некоторые классы по своей природе должны быть конечными, слишком часто близорукий разработчик просто помечает все листовые классы в дереве наследования как final.

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

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

2 голосов
/ 15 января 2011

Почему он так редко используется в дикой природе?

Это не соответствует моему опыту.Я вижу, что это очень часто используется во всех видах библиотек.Только один (случайный) пример: посмотрите на абстрактные классы в:

http://code.google.com/p/guava-libraries/

, например, com.google.common.collect.AbstractIterator.peek(), hasNext(), next() и endOfData() являются окончательными, оставляя только computeNext() разработчику.Это очень распространенный пример IMO.

Основная причина против использования final заключается в том, чтобы позволить разработчикам изменять алгоритм - вы упомянули шаблон «метод шаблона»: все же может иметь смысл изменить метод шаблонаили улучшить его с помощью некоторых действий до / после (не рассылая весь класс десятками хуков до / после).

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

2 голосов
/ 15 января 2011

Я думаю, что это обычно не используется по двум причинам:

  1. Люди не знают, что оно существует
  2. Люди не имеют привычки думать об этом при создании метода.

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

1 голос
/ 15 января 2011

Почему он так редко используется в дикой природе?

Потому что это не должно быть необходимо. Он также не полностью закрывает реализацию, поэтому может дать ложное чувство безопасности.

В этом нет необходимости из-за принципа замены Лискова . У метода есть контракт и в правильно спроектированной диаграмме наследования этот контракт выполнен (в противном случае это ошибка). Пример:

interface Animal {
    void bark();
}

abstract class AbstractAnimal implements Animal{
   final void bark() {
       playSound("whoof.wav"); // you were thinking about a dog, weren't you?
   }
}

class Dog extends AbstractAnimal {
  // ok
}

class Cat extends AbstractAnimal() {
  // oops - no barking allowed!
}

Не позволяя подклассу делать правильные вещи (для него), вы можете ввести ошибку. Или же вам может потребоваться, чтобы другой разработчик поместил дерево наследования вашего Garble интерфейса рядом с вашим, потому что ваш последний метод не позволяет ему делать то, что он должен делать.

Ложное чувство безопасности типично для нестатического финального метода. Статический метод не должен использовать состояние из экземпляра (он не может). Нестатический метод, вероятно, делает. Ваш последний (нестатический) метод, вероятно, тоже имеет значение, но он не владеет переменными экземпляра - они могут отличаться от ожидаемых. Таким образом, вы добавляете бремя для разработчика формы наследования класса AbstractGarble - чтобы гарантировать, что поля экземпляра находятся в состоянии, ожидаемом вашей реализацией в любой момент времени. Не давая разработчику возможности подготовить состояние перед вызовом вашего метода, как в:

int zblah() {
    prepareState();
    return super.zblah();
}

По моему мнению, вам не следует закрывать реализацию таким образом, если у вас нет очень веских причин. Если вы задокументируете свой контракт на метод и предоставите тестовый набор, вы сможете доверять другим разработчикам. Используя тест Junit, они могут фактически проверить принцип подстановки Лискова .

В качестве примечания я иногда закрываю метод. Особенно, если он находится на граничной части каркаса. Мой метод ведет некоторую бухгалтерию, а затем продолжает абстрактный метод, который будет реализован кем-то другим:

final boolean login() {
    bookkeeping();
    return doLogin();
}
abstract boolean doLogin();

Таким образом, никто не забывает вести бухгалтерию, но может предоставить пользовательский логин. Нравится ли вам такая настройка, конечно, зависит от вас:)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...