Как кратко описано здесь, переопределение закрытых методов в Java недопустимо, потому что закрытые методы родительского класса являются "автоматически финалом, и скрытый от производного класса". Мой вопрос является в основном академическим.
То, как это не нарушение инкапсуляции, чтобы не позволить закрытому методу родителя быть "переопределенным" (т.е., реализовало независимо, с той же подписью, в дочернем классе)? К закрытому методу родителя не может получить доступ или наследовать дочерний класс, в соответствии с принципами инкапсуляции. Это скрыто.
Так, почему дочерний класс должен быть ограничен в реализации его собственного метода с тем же именем/подписью? Существует ли хорошая теоретическая основа для этого, или это - просто прагматическое какое-то решение? Другие языки (C++ или C#) имеют различные правила об этом?
Вы не можете переопределить частный метод, но вы можете ввести его в производный класс без проблем. Это прекрасно компилируется:
class Base
{
private void foo()
{
}
}
class Child extends Base
{
private void foo()
{
}
}
Обратите внимание, что если вы попытаетесь применить @Override
аннотацию к Child.foo()
, то получите ошибку во время компиляции. Пока ваш компилятор/IDE настроен на выдачу предупреждений или ошибок, если вы пропустили аннотацию @Override
, все должно быть в порядке. Правда, я предпочитаю C# подход, в котором ключевым словом является override
, но на Java это делать явно поздно.
Что касается обработки C# "переопределения" private-метода - private-метод не может быть вообще виртуальным, но в базовом классе, конечно же, можно внедрить новый private-метод с тем же именем, что и private-метод.
.Когда метод является частным, он не виден ребенку. Поэтому нет смысла его переопределять.
Родительский метод не может быть доступен или унаследован дочерним классом, в соответствии с принципами инкапсуляции. Он скрыт.
Так почему же дочерний класс должен быть ограниченного в реализации собственного метод с тем же именем/подписью?
Нет такого ограничения. Это можно сделать без проблем, просто это не называется "переопределение".
Переопределенные методы подвержены динамической диспетчеризации, т.е. фактически вызываемый метод выбирается во время выполнения в зависимости от фактического типа вызываемого объекта. С частным методом этого не происходит (и не должно происходить, согласно вашему первому утверждению). И именно это подразумевается под выражением "private методы не могут быть переопределены"
.Я думаю, ты неправильно истолковываешь то, что написано в этом посте. Это не говорит о том, что дочерний класс "ограничен в реализации своего собственного метода с таким же именем/подписью"
Вот код, слегка отредактированный:
public class PrivateOverride {
private static Test monitor = new Test();
private void f() {
System.out.println("private f()");
}
public static void main(String[] args) {
PrivateOverride po = new Derived();
po.f();
});
}
}
class Derived extends PrivateOverride {
public void f() {
System.out.println("public f()");
}
}
И цитата:
Вы можете обоснованно ожидать, что результат будет "public f( )",
Причина этой цитаты в том, что переменная po
на самом деле содержит экземпляр Derived. Однако, так как метод определен как private, компилятор на самом деле смотрит на тип переменной, а не на тип объекта. И он транслирует вызов метода в invokespecial (думаю, это правильный опкод, не проверил спецификацию JVM), а не в invkeinstance.
Класс определяется тем, какие методы он делает доступными и как они ведут себя. А не тем, как они реализованы внутри класса (например, с помощью обращений к частным методам).
Так как инкапсуляция имеет отношение к поведению, а не к деталям реализации, то частные методы не имеют никакого отношения к инкапсуляции идеи. В некотором смысле, ваш вопрос не имеет смысла. Это как спросить "Как положить сливки в кофе, а не нарушить инкапсуляцию?"
Предположительно частный метод используется чем-то, что является публичным. Вы можете отменить это. При этом вы изменили свое поведение.
Приношу свои извинения за неправильное и не соответствующее моему описанию использование термина "переопределить". Мое описание описывает сценарий. Следующий код расширяет пример Джона Скита для отображения моего сценария:
class Base {
public void callFoo() {
foo();
}
private void foo() {
}
}
class Child extends Base {
private void foo() {
}
}
Использование похоже на следующее:
Child c = new Child();
c.callFoo();
Проблема, с которой я столкнулся, заключается в том, что вызывался родительский метод foo(), хотя, как видно из кода, я вызывал callFoo() на дочерней переменной экземпляра. Я думал, что определяю в функции Child() новый private метод foo(), который будет вызываться методом callFoo(), но думаю, что некоторые из сказанного kdgregory могут быть применимы к моему сценарию - возможно, из-за того, как конструктор производного класса вызывает super(), а возможно и нет.
В Eclipse не было предупреждения компилятора, и код действительно компилировался. Результат был неожиданным
.] [] [] "Имеют ли другие языки (C++ или C#) иные правила?" [
]. [
] Ну, у C++ разные правила: статический или динамический процесс связывания членских функций и применение привилегий доступа ортогональны.[
]. []Придание функции-члену статуса []private[
] modifier привилегий доступа означает, что эта функция может быть вызвана только ее декларирующим классом, а не другими (даже производными классами). Когда Вы объявляете функцию-член []private[
] членом как []virtual[
], даже виртуальную ([]virtual void foo() = 0;[
]), Вы разрешаете базовому классу использовать привилегии доступа.[
] Когда речь заходит о функциях члена [] virtual[
], привилегии доступа говорят вам, что вы должны делать: [
]private virtual[
] означает, что можно специализироваться на поведении, но вызов функции-член производится базовым классом, конечно же, управляемым способом[] protected virtual[
] означает, что вы должны/должны вызывать версию функции-член верхнего класса, когда переопределяете ее[] Таким образом, в языке С++ привилегии доступа и виртуальность не зависят друг от друга. Определение того, должна ли функция быть статически или динамически связана, является последним шагом в разрешении вызова функции.[
]. [] Наконец, шаблон проектирования шаблонного метода должен быть предпочтительнее [] публичных виртуальных [
] членских функций. [
] Справка: []Conversations: Virtually Yours[][
] [] В статье приводится практическое использование функции члена [] private virtual [
] member.[
]ISO/IEC 14882-2003 §3.4.1 [
] [] [] [] Поиск имени может ассоциировать несколько деклараций с именем, если он находит, что имя является именем функции; говорят, что декларации формируют набор перегруженных функций (13.1). Разрешение перегрузки (13.3) происходит после успешного поиска имени. Правила доступа (п. 11) рассматриваются только после того, как поиск имени и разрешение перегрузки функции (если применимо) увенчались успехом. Только после поиска имени, разрешения перегрузки функции (если применимо) и проверки доступа, атрибуты, введенные объявлением имени, используются в дальнейшем при обработке выражений (п. 5). [
] [
]ISO/IEC 14882-2003 §5.2.2 [
] [] []] Функция, вызываемая при вызове член-функции, обычно выбирается в соответствии со статическим типом выражения объекта (п. 10), но если эта функция является виртуальной и не указана с помощью аквалифицированного - тогда фактически вызываемая функция будет конечным переопределителем (10.3) выбранной функции в динамическом типе выражения объекта [Примечание: динамический тип - это тип объекта, на который указывает или на который ссылается текущее значение выражения объекта.[
]. [
Что ж, разрешение перезаписи частных методов вызовет либо утечку инкапсуляции, либо угрозу безопасности. Если мы предположим, что это возможно , то мы получим следующую ситуацию:
Допустим, есть частный метод boolean hasCredentials ()
, тогда расширенный класс может просто переопределить это так:
логическое hasCredentials () {return true; }
, таким образом нарушив проверку безопасности.
Единственный способ предотвратить это для исходного класса - объявить его метод final
. Но теперь это утечка информации о реализации через инкапсуляцию, потому что производный класс теперь не может больше создавать метод hasCredentials
- он будет конфликтовать с тем, который определен в базовом классе.
Это плохо: допустим, этого метода сначала не существует в Base
. Теперь разработчик может законно создать класс Derived
и дать ему метод hasCredentials
, который работает должным образом.
Но теперь выпущена новая версия исходного Базового
класса. Его открытый интерфейс не изменяется (как и его инварианты), поэтому мы должны ожидать, что он не нарушит существующий код. Только так, потому что теперь есть конфликт имени с методом в производном классе.
Я думаю, что вопрос проистекает из недоразумения:
Как это / не / нарушение инкапсуляции не позволяет «переопределить» родительский частный метод (т. дочерний класс)
Текст в круглых скобках - это , противоположный тексту перед ним. Java позволяет «независимо реализовать [частный метод] с той же сигнатурой в дочернем классе». Если этого не допустить, это нарушит инкапсуляцию, как я объяснил выше.
Но «запретить« переопределить »родительский частный метод» - это нечто иное и необходимо для обеспечения инкапсуляции .