Цель окончательная и герметичная - PullRequest
9 голосов
/ 20 сентября 2009

Почему кто-то хочет пометить класс как окончательный или запечатанный?

Ответы [ 3 ]

14 голосов
/ 20 сентября 2009

Согласно Википедии , «Запечатанные классы в основном используются для предотвращения деривации. Они добавляют еще один уровень строгости во время компиляции, улучшают использование памяти и запускают определенные оптимизации, повышающие эффективность во время выполнения».

Также из в блоге Патрика Смаккья :

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

  • Производительность: (…) если JIT-компилятор видит вызов виртуального метода с использованием закрытых типов, JIT-компилятор может создавать более эффективный код, вызывая метод не виртуально. (…)

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

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

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

10 голосов
/ 20 сентября 2009

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

Нужно ли расширять тип - это решение разработчика, который его создал, а не разработчика, который придет позже и хочет его расширить.

6 голосов
/ 21 сентября 2009

Джошуа Блох в своей книге «Эффективная Java» рассказывает об этом. Он говорит «документ на наследство или запретить его». Дело в том, что класс является своего рода контрактом между автором и клиентом. Разрешение клиенту наследовать от базового класса делает этот контракт намного более строгим. Если вы собираетесь наследовать от него, вы, скорее всего, собираетесь переопределить некоторые методы, в противном случае вы можете заменить наследование композицией. Какие методы могут быть переопределены, и что вы должны сделать для их реализации - должны быть задокументированы, иначе ваш код может привести к непредсказуемым результатам. Насколько я помню, он показывает такой пример - вот класс коллекции с методами

public interface Collection<E> extends Iterable<E> {    
  ...
  boolean add(E e);
  boolean addAll(Collection<? extends E> c);
  ...
}

Существует некоторая реализация, то есть ArrayList. Теперь вы хотите наследовать от него и переопределить некоторые методы, чтобы он выводил на консоль сообщение при добавлении элемента. Теперь вам нужно переопределить add и addAll или только add ? Это зависит от того, как addAll реализован - работает ли он напрямую с внутренним состоянием (как это делает ArrayList) или вызывает add (как это делает AbstractCollection). Или, может быть, есть addInternal , который вызывается как add , так и addAll . Таких вопросов не было, пока вы не решили унаследовать этот класс. Если вы просто используете его - это вас не беспокоит. Таким образом, автор класса должен задокументировать это, если он хочет, чтобы вы унаследовали его класс.

А что если он захочет изменить реализацию в будущем? Если его класс используется, а не наследуется, ничто не мешает ему изменить реализацию на более эффективную. Теперь, если вы унаследовали от этого класса, посмотрели на источник и обнаружили, что addAll вызывает add , вы переопределяете только add . Позже автор меняет реализацию так, чтобы addAll больше не вызывал add - ваша программа не работает, сообщение не печатается при вызове addAll . Или вы посмотрели на источник и обнаружили, что addAll не вызывает add, поэтому вы переопределяете add и addAll . Теперь автор меняет реализацию, поэтому addAll вызывает добавить - ваша программа снова прерывается, когда addAll вызывается, сообщение печатается дважды для каждого элемента.

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

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

Поэтому в основном комментарий «Потому что класс не хочет иметь детей, и мы должны уважать его желания», является правильным.

Итак, кто-то хочет пометить класс как окончательный / запечатанный, когда он считает, что возможные изменения в деталях реализации более ценны, чем наследование. Существуют и другие способы достижения результатов, аналогичных наследованию.

...