Веские причины запретить наследование в Java?

Каковы веские причины запретить наследование в Java, например, используя классы final или классы с помощью одного закрытого конструктора без параметров? Каковы веские причины сделать метод окончательным?

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

John Topley 20.10.2008 19:21

Разве это не «неявный конструктор по умолчанию»?

cretzel 20.10.2008 19:28

«Вам не нужно предоставлять какие-либо конструкторы для вашего класса, но вы должны быть осторожны при этом. Компилятор автоматически предоставляет конструктор по умолчанию без аргументов для любого класса без конструкторов». java.sun.com/docs/books/tutorial/java/javaOO/constructors.ht‌ ml - Джон Топли

John Topley 20.10.2008 19:37
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
186
3
62 473
11
Перейти к ответу Данный вопрос помечен как решенный

Ответы 11

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

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

Nav 25.02.2016 11:11

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

Bill the Lizard 25.02.2016 15:11
Ответ принят как подходящий

Лучше всего вам сюда обратиться к пункту 19 прекрасной книги Джошуа Блоха «Эффективная Java», который называется «Разработайте и задокументируйте наследование, иначе запретите это». (Это пункт 17 во втором издании и пункт 15 в первом издании.) Вы действительно должны его прочитать, но я резюмирую.

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

Таким образом, классы должны быть двух видов:

  1. Классы разработан как расширенный и с достаточной документацией, чтобы описать, как это должно быть сделано

  2. Классы отмечен окончательный

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

Это хороший ответ, но в большинстве ситуаций это немного излишне.

wprl 20.10.2008 19:12

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

Eric Weilnau 20.10.2008 19:36

Чтобы немного разобраться в «Эффективной Java», вам нужно понять взгляды Джоша на дизайн. Он говорит [что-то вроде] вы всегда должны проектировать интерфейсы классов, как если бы они были общедоступным API. Я думаю, что большинство считает, что этот подход обычно слишком тяжелый и негибкий. (ЯГНИ и др.)

Tom Hawtin - tackline 20.10.2008 19:44

Хороший ответ. (Я не могу не указать, что это шесть символов, так как есть еще пробел ... ;-)

joel.neely 08.02.2009 16:18

Обратите внимание, что завершение классов может усложнить тестирование (сложнее заглушить или имитировать).

notnoop 17.12.2009 01:59

Если последний класс пишет в интерфейс, насмешки не должны быть проблемой.

Fred Haslam 18.05.2010 20:29

очень сложно удалить финал на интерфейсе, для которого у вас нет исходного кода, и возможность подкласса для переопределения метода может иметь важное значение.

Thorbjørn Ravn Andersen 09.07.2010 07:44

Чтобы спасти людей, ищущих его, «Разработайте и задокументируйте для наследования, иначе запретите это» - это пункт 17 второго издания.

freshtop 09.12.2014 04:01

@ ThorbjørnRavnAndersen, ты имеешь в виду класс? Интерфейс (или даже абстрактный класс) с модификатором final не выглядит хорошим дизайном.

amertkara 29.04.2015 18:39

Не в этом разница между разработчиками библиотек / фреймворков и разработчиками приложений. То, что Том Хотин называет «слишком тяжелым и негибким», становится критически важным, когда вы наследуете код, написанный другими, и не можете отказаться от каких-либо ошибок в дизайне, потому что первоначальные разработчики считали, что все должно быть публичным. В общем, вы всегда должны разрабатывать конечные классы уровня пакета, которые реализуют методы в общедоступных интерфейсах. Если вам понадобится изменить интерфейс позже, используйте АДАПТЕР. Невыполнение этого требования означает, что ошибки проектирования не могут быть исправлены после развертывания программного обеспечения. т.е. Java

Visionary Software Solutions 19.07.2015 23:15

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

GuyPaddock 27.10.2017 20:16

Я начал читать "Эффективную Java", о которой упоминается в этом посте. Было бы неплохо понять передовой опыт

Rakesh L 18.03.2018 19:15

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

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

См. Пункты 16 и 17 «Эффективная Java 2-го издания» или мою запись в блоге "Налог на наследство".

Ссылка на сообщение в блоге не работает. Кроме того, не могли бы вы подробнее рассказать об этом?

user289086 22.09.2014 09:27

@MichaelT: исправлена ​​ссылка, спасибо - следите за ней, чтобы узнать подробности :) (боюсь, у меня нет времени что-то добавлять прямо сейчас.)

Jon Skeet 22.09.2014 09:46

@JonSkeet, ссылка на пост в блоге битая.

Lucifer 27.03.2015 04:31

@Kedarnath: У меня работает ... был какое-то время не работает, но теперь указывает на правильную страницу на codeblog.jonskeet.uk ...

Jon Skeet 27.03.2015 09:44

Чтобы помешать людям делать то, что может сбить с толку себя и других. Представьте себе физическую библиотеку, в которой у вас есть определенные константы или вычисления. Без использования ключевого слова final кто-нибудь может прийти и переопределить базовые вычисления или константы, которые НИКОГДА не должны меняться.

Есть кое-что, чего я не понимаю в этом аргументе - если кто-то изменил те вычисления / константы, которые не должны меняться - разве все сбои из-за этого на 100% не являются его ошибкой? Другими словами, какая польза от этого изменения?

matt b 20.10.2008 19:18

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

Anson Smith 20.10.2008 19:28

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

Eric Weilnau 20.10.2008 19:33

На самом деле это использование слова «final», отличное от того, о котором спрашивали.

DJClayworth 20.10.2008 21:53

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

joel.neely 08.02.2009 16:25

Хммм ... Я могу думать о двух вещах:

У вас может быть класс, который занимается определенными проблемами безопасности. Создав подклассы и снабдив вашу систему подклассами, злоумышленник может обойти ограничения безопасности. Например. ваше приложение может поддерживать плагины, и если плагин может просто подклассифицировать ваши классы, относящиеся к безопасности, он может использовать этот трюк, чтобы каким-то образом протащить его подклассифицированную версию на место. Однако Sun скорее должна иметь дело с апплетами и т.п.

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

 String blah = someOtherString;

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

вы можете захотеть создать неизменяемые объекты (http://en.wikipedia.org/wiki/Immutable_object), вы можете захотеть создать синглтон (http://en.wikipedia.org/wiki/Singleton_pattern), или вы можете захотеть запретить кому-либо переопределить метод из соображений эффективности, безопасности или безопасности.

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

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

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

Miguel Ping 20.10.2008 21:53

Разница в генерации кода - не миф, но, возможно, повышение производительности. Как я уже сказал, это небольшой выигрыш, один сайт упомянул о приросте производительности на 3,5%, а на некоторых выше, что в большинстве случаев не стоит того, чтобы использовать весь свой код нацистским.

seanalltogether 21.10.2008 05:07

Это не миф. Фактически, компилятор может встроить только методы V final. Я не уверен, что JIT «встроены» во время выполнения искусства, но я сомневаюсь, что это возможно, поскольку позже вы, возможно, загрузите производный класс.

Uri 01.11.2008 20:51

Микробенчмарк == крупица соли. Тем не менее, после разогрева цикла 10B, среднее число вызовов более 10B практически не различается в Eclipse на моем ноутбуке. Два метода возвращают String, final - 6,9643us, non-final - 6,9641us. Эта дельта - фоновый шум, ИМХО.

joel.neely 08.02.2009 17:17

Есть 3 варианта использования, в которых вы можете использовать final методы.

  1. Чтобы производный класс не переопределял функциональность определенного базового класса.
  2. Это сделано в целях безопасности, когда базовый класс предоставляет некоторые важные основные функции фреймворка, а производный класс не должен его изменять.
  3. Final методы быстрее, чем методы экземпляра, поскольку для final и private методов не используется концепция виртуальной таблицы. Так что везде, где есть возможность, старайтесь использовать final методы.

Назначение финального класса:

Чтобы никто не мог расширить эти классы и изменить их поведение.

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

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

Rajkiran 20.01.2015 21:10

Даже если вы сделаете все переменные и методы окончательными, другие могут унаследовать ваш класс и добавить дополнительные функции и изменить базовое поведение вашего класса.

user1923551 07.05.2015 12:18

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

Siddhartha 06.07.2017 01:03

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

Другие вопросы по теме