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




Возможно, вы захотите сделать метод окончательным, чтобы классы переопределения не могли изменить поведение, на которое рассчитывают в других методах. Методы, вызываемые в конструкторах, часто объявляются final, поэтому при создании объектов вы не получите неприятных сюрпризов.
Не совсем понял этот ответ. Если вы не возражаете, не могли бы вы объяснить, что вы имеете в виду, говоря «переопределение классов не может изменить поведение, на которое рассчитывают в других методах»? Я спрашиваю, во-первых, потому что я не понял предложения, а во-вторых, потому что Netbeans рекомендует, чтобы одна из моих функций, которые я вызываю из конструктора, должна быть final.
@Nav Если вы сделаете метод окончательным, то класс-замещающий не сможет иметь свою собственную версию этого метода (или это будет ошибка компиляции). Таким образом, вы знаете, что поведение, которое вы реализуете в этом методе, не изменится позже, когда кто-то расширит класс.
Лучше всего вам сюда обратиться к пункту 19 прекрасной книги Джошуа Блоха «Эффективная Java», который называется «Разработайте и задокументируйте наследование, иначе запретите это». (Это пункт 17 во втором издании и пункт 15 в первом издании.) Вы действительно должны его прочитать, но я резюмирую.
Взаимодействие унаследованных классов с их родителями может быть неожиданным и непредсказуемым, если предок не предназначен для наследования.
Таким образом, классы должны быть двух видов:
Классы разработан как расширенный и с достаточной документацией, чтобы описать, как это должно быть сделано
Классы отмечен окончательный
Если вы пишете чисто внутренний код, это может быть излишним. Однако дополнительные усилия, связанные с добавлением пяти символов в файл класса, очень малы. Если вы пишете только для внутреннего потребления, будущий программист всегда может удалить «финал» - вы можете думать об этом как о предупреждении, говорящем, что «этот класс не был разработан с учетом наследования».
Это хороший ответ, но в большинстве ситуаций это немного излишне.
По моему опыту, это не перебор, если кто-то, кроме меня, будет использовать код. Я никогда не понимал, почему в Java есть методы, переопределяемые по умолчанию.
Чтобы немного разобраться в «Эффективной Java», вам нужно понять взгляды Джоша на дизайн. Он говорит [что-то вроде] вы всегда должны проектировать интерфейсы классов, как если бы они были общедоступным API. Я думаю, что большинство считает, что этот подход обычно слишком тяжелый и негибкий. (ЯГНИ и др.)
Хороший ответ. (Я не могу не указать, что это шесть символов, так как есть еще пробел ... ;-)
Обратите внимание, что завершение классов может усложнить тестирование (сложнее заглушить или имитировать).
Если последний класс пишет в интерфейс, насмешки не должны быть проблемой.
очень сложно удалить финал на интерфейсе, для которого у вас нет исходного кода, и возможность подкласса для переопределения метода может иметь важное значение.
Чтобы спасти людей, ищущих его, «Разработайте и задокументируйте для наследования, иначе запретите это» - это пункт 17 второго издания.
@ ThorbjørnRavnAndersen, ты имеешь в виду класс? Интерфейс (или даже абстрактный класс) с модификатором final не выглядит хорошим дизайном.
Не в этом разница между разработчиками библиотек / фреймворков и разработчиками приложений. То, что Том Хотин называет «слишком тяжелым и негибким», становится критически важным, когда вы наследуете код, написанный другими, и не можете отказаться от каких-либо ошибок в дизайне, потому что первоначальные разработчики считали, что все должно быть публичным. В общем, вы всегда должны разрабатывать конечные классы уровня пакета, которые реализуют методы в общедоступных интерфейсах. Если вам понадобится изменить интерфейс позже, используйте АДАПТЕР. Невыполнение этого требования означает, что ошибки проектирования не могут быть исправлены после развертывания программного обеспечения. т.е. Java
Моя борьба с пометкой всего как окончательного по умолчанию заключается в том, что он предполагает, что разработчик, который не приложил усилий, чтобы сделать класс подходящим для расширения, каким-то образом потратит время на то, чтобы убедиться, что он реализует хорошо спроектированный интерфейс для облегчения тестирования. Либо разработчик ленив, и в этом случае интерфейса нет и он помечен как final, либо разработчик ориентирован на детали, и в этом случае класс поддерживает расширение и не обязательно должен быть final.
Я начал читать "Эффективную Java", о которой упоминается в этом посте. Было бы неплохо понять передовой опыт
Одна из причин сделать класс окончательным - если вы хотите принудительно использовать композицию вместо наследования. Обычно это желательно, чтобы избежать тесной связи между классами.
Наследование похоже на бензопилу - очень мощную, но ужасную в чужих руках. Либо вы создаете класс для наследования (что может ограничить гибкость и занять намного больше времени), либо вам следует запретить его.
См. Пункты 16 и 17 «Эффективная Java 2-го издания» или мою запись в блоге "Налог на наследство".
Ссылка на сообщение в блоге не работает. Кроме того, не могли бы вы подробнее рассказать об этом?
@MichaelT: исправлена ссылка, спасибо - следите за ней, чтобы узнать подробности :) (боюсь, у меня нет времени что-то добавлять прямо сейчас.)
@JonSkeet, ссылка на пост в блоге битая.
@Kedarnath: У меня работает ... был какое-то время не работает, но теперь указывает на правильную страницу на codeblog.jonskeet.uk ...
Чтобы помешать людям делать то, что может сбить с толку себя и других. Представьте себе физическую библиотеку, в которой у вас есть определенные константы или вычисления. Без использования ключевого слова final кто-нибудь может прийти и переопределить базовые вычисления или константы, которые НИКОГДА не должны меняться.
Есть кое-что, чего я не понимаю в этом аргументе - если кто-то изменил те вычисления / константы, которые не должны меняться - разве все сбои из-за этого на 100% не являются его ошибкой? Другими словами, какая польза от этого изменения?
Да, технически это «их» вина, но представьте, что кто-то другой, использующий библиотеку, не осведомлен об изменениях, может привести к путанице. Я всегда думал, что это было причиной того, что многие из базовых классов Java были помечены как окончательные, чтобы люди не меняли базовую функциональность.
@matt b - Похоже, вы предполагаете, что разработчик, внесший изменение, сделал это сознательно или что его будет заботить, что это их вина. Если кто-то, кроме меня, будет использовать мой код, я помечу его как окончательный, если он не предназначен для изменения.
На самом деле это использование слова «final», отличное от того, о котором спрашивали.
Этот ответ, кажется, путает наследование и изменчивость отдельных полей с возможностью писать подкласс. Вы можете пометить отдельные поля и методы как окончательные (предотвращая их переопределение), не запрещая наследование.
Хммм ... Я могу думать о двух вещах:
У вас может быть класс, который занимается определенными проблемами безопасности. Создав подклассы и снабдив вашу систему подклассами, злоумышленник может обойти ограничения безопасности. Например. ваше приложение может поддерживать плагины, и если плагин может просто подклассифицировать ваши классы, относящиеся к безопасности, он может использовать этот трюк, чтобы каким-то образом протащить его подклассифицированную версию на место. Однако Sun скорее должна иметь дело с апплетами и т.п.
Гораздо более реалистичный способ - избежать того, чтобы объект стал изменчивым. Например. поскольку строки неизменяемы, ваш код может безопасно хранить ссылки на них
String blah = someOtherString;
вместо того, чтобы сначала скопировать строку. Однако, если вы можете создать подкласс String, вы можете добавить к нему методы, которые позволяют изменять строковое значение, теперь никакой код больше не может полагаться, что строка останется такой же, если она просто скопирует строку, как указано выше, вместо этого он должен дублировать нить.
вы можете захотеть создать неизменяемые объекты (http://en.wikipedia.org/wiki/Immutable_object), вы можете захотеть создать синглтон (http://en.wikipedia.org/wiki/Singleton_pattern), или вы можете захотеть запретить кому-либо переопределить метод из соображений эффективности, безопасности или безопасности.
Кроме того, если вы пишете коммерческий класс с закрытым исходным кодом, вы можете не захотеть, чтобы люди могли изменять функциональность в дальнейшем, особенно если вам нужно оказать ему поддержку, а люди переопределили ваш метод и жалуются, что его вызов дает неожиданные результаты.
Если вы пометите классы и методы как окончательные, вы можете заметить небольшой прирост производительности, поскольку среде выполнения не нужно искать правильный метод класса для вызова для данного объекта. Неокончательные методы помечаются как виртуальные, чтобы их можно было должным образом расширить при необходимости, финальные методы могут быть напрямую связаны или скомпилированы в классе.
Я считаю, что это миф, поскольку виртуальные машины текущего поколения должны иметь возможность оптимизировать и деоптимизировать по мере необходимости. Я помню, как кто-то заклеймил это и классифицировал как миф.
Разница в генерации кода - не миф, но, возможно, повышение производительности. Как я уже сказал, это небольшой выигрыш, один сайт упомянул о приросте производительности на 3,5%, а на некоторых выше, что в большинстве случаев не стоит того, чтобы использовать весь свой код нацистским.
Это не миф. Фактически, компилятор может встроить только методы V final. Я не уверен, что JIT «встроены» во время выполнения искусства, но я сомневаюсь, что это возможно, поскольку позже вы, возможно, загрузите производный класс.
Микробенчмарк == крупица соли. Тем не менее, после разогрева цикла 10B, среднее число вызовов более 10B практически не различается в Eclipse на моем ноутбуке. Два метода возвращают String, final - 6,9643us, non-final - 6,9641us. Эта дельта - фоновый шум, ИМХО.
Есть 3 варианта использования, в которых вы можете использовать final методы.
Назначение финального класса:
Чтобы никто не мог расширить эти классы и изменить их поведение.
Например: класс оболочки Integer - это последний класс. Если этот класс не является окончательным, то любой может расширить Integer до своего собственного класса и изменить базовое поведение целочисленного класса. Чтобы избежать этого, java сделала все классы-оболочки как окончательные классы.
Не очень уверен в вашем примере. Если это так, то почему бы не сделать методы и переменные окончательными вместо того, чтобы сделать окончательным весь класс. Не могли бы вы отредактировать свой ответ, указав детали.
Даже если вы сделаете все переменные и методы окончательными, другие могут унаследовать ваш класс и добавить дополнительные функции и изменить базовое поведение вашего класса.
> Если этот класс не является окончательным, то любой может расширить Integer до своего собственного класса и изменить базовое поведение целочисленного класса. Допустим, это было разрешено, и этот новый класс назывался DerivedInteger, он по-прежнему не меняет исходный класс Integer, и тот, кто использует DerivedInteger, делает это на свой страх и риск, поэтому я до сих пор не понимаю, почему это проблема.
Вы хотите сделать метод окончательным, чтобы переопределение классов не меняло его поведения. Если вы хотите изменить поведение, сделайте метод общедоступным. Когда вы переопределяете общедоступный метод, его можно изменить.
Педантизм: конструктор по умолчанию - это тот, который сгенерирован для вас компилятором Java, если вы явно не написали его в своем коде. Вы имеете в виду конструктор без аргументов.