Подтвердить что ты не робот

Переопределение частных методов в Java

Как кратко описано здесь, переопределение частных методов в Java недопустимо, поскольку частные методы родительского класса "автоматически окончательны и скрыты от производных класс". Мой вопрос в основном академический.

Как это не является нарушением инкапсуляции, чтобы не допускать "переопределенный" родительский частный метод (т.е. реализованный независимо с той же подписью в дочернем классе)? Родительский частный метод не может быть доступен или унаследован дочерним классом в соответствии с принципами инкапсуляции. Он скрыт.

Итак, почему дочерний класс должен быть ограничен от реализации его собственного метода с тем же именем/подписями? Есть ли хорошая теоретическая основа для этого, или это просто прагматическое решение? Существуют ли на других языках (С++ или С#) разные правила?

4b9b3361

Ответ 1

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

class Base
{
   private void foo()
   {
   }
}

class Child extends Base
{
    private void foo()
    {
    }
}

Обратите внимание, что если вы попытаетесь применить аннотацию @Override к Child.foo(), вы получите ошибку времени компиляции. До тех пор, пока у вас установлен ваш компилятор /IDE, чтобы дать вам предупреждения или ошибки, если вам не хватает аннотации @Override, все должно быть хорошо. По общему признанию, я предпочитаю, чтобы подкласс С# override был ключевым словом, но было явно слишком поздно делать это на Java.

Что касается обработки С# "переопределения" частного метода - частный метод не может быть виртуальным в первую очередь, но вы можете, конечно, ввести новый закрытый метод с тем же именем, что и частный метод в базовом классе.

Ответ 2

Ну, что позволяет перезаписывать частные методы, может вызвать утечку инкапсуляции или угрозу безопасности. Если предположить, что он возможен, тогда wed получим следующую ситуацию:

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

    boolean hasCredentials() { return true; }
    

    тем самым нарушив проверку безопасности.

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

    Это плохо: скажем, что этот метод не существует сначала в Base. Теперь разработчик может законно получить класс Derived и дать ему метод hasCredentials, который работает как ожидалось.

    Но теперь выпущена новая версия исходного класса Base. Его открытый интерфейс не меняет (и не делает его инвариантов), поэтому мы должны ожидать, что он не сломает существующий код. Только это происходит, потому что теперь theres сталкивается имя с методом в производном классе.

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

Как это/нет/нарушение инкапсуляции не допускать "переопределения" родительского частного метода (т.е. независимо друг от друга с той же сигнатурой в дочернем классе)

Текст внутри круглых скобок является противоположностью текста перед ним. Java позволяет вам "самостоятельно реализовать [частный метод] с той же сигнатурой в дочернем классе". Не позволяя этому нарушать инкапсуляцию, как объяснил Ive выше.

Но "чтобы не допускать переопределение родительского частного метода", это нечто иное и необходимое для обеспечения инкапсуляции.

Ответ 3

"У других языков (С++ или С#) есть разные правила?"

Ну, у С++ есть разные правила: процесс связывания статической или динамической членской функции и принудительные действия привилегий доступа ортогональны.

Предоставление функции-члена mod > t20 > означает, что эта функция может быть вызвана только классом объявления, а не другими (даже не производными классами). Когда вы объявляете функцию-член private как virtual, даже чистую виртуальную (virtual void foo() = 0;), вы позволяете базовому классу извлекать выгоду из специализации, сохраняя при этом права доступа.

Когда дело доходит до virtual функций-членов, привилегии доступа сообщают вам, что вы должны делать:

  • private virtual означает, что вам разрешено специализировать поведение, но вызов функции-члена производится базовым классом, безусловно, контролируемым образом.
  • protected virtual означает, что вы должны/должны вызывать версию класса-члена верхнего класса при ее переопределении

Итак, в С++ привилегии доступа и виртуальность независимы друг от друга. Определение необходимости статической или динамической привязки функции - последний шаг в разрешении вызова функции.

Наконец, шаблон шаблона метода шаблона должен быть предпочтительнее функций-членов public virtual.

Ссылка: Беседы: практически ваши

В статье приведено практическое использование функции члена private virtual.


ISO/IEC 14882-2003 §3.4.1

Поиск имени может связывать более одного объявления с именем, если он считает имя именем функции; говорят, что объявления образуют набор перегруженных функций (13.1). Разрешение перегрузки (13.3) происходит после успешного поиска имени. Правила доступа (раздел 11) рассматриваются только после успешного поиска имени и разрешения перегрузки функции (если применимо). Только после поиска имени, разрешение перегрузки функции (если применимо) и проверка доступа были успешными, являются атрибутами, введенными объявлением имен, используемым далее в обработке выражений (раздел 5).

ISO/IEC 14882-2003 §5.2.2

Функция, вызываемая в вызове функции-члена, обычно выбирается в соответствии со статическим типом выражения объекта (раздел 10), но если эта функция isvirtualand не указана с использованием aqualified-idthen, то фактически вызванная функция будет окончательным перераспределением ( 10.3) выбранной функции в динамическом типе выражения объекта [Примечание: динамический тип - это тип объекта, на который указывает или ссылается текущее значение выражения объекта.

Ответ 4

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

Итак, почему класс child должен быть ограничено метод с тем же именем/сигнатурой?

Нет такого ограничения. Вы можете сделать это без каких-либо проблем, это просто не называется "переопределением".

Переопределенные методы подлежат динамической отправке, то есть метод, который фактически вызывается, выбирается во время выполнения в зависимости от фактического типа объекта, на который он вызвал. С помощью частного метода этого не происходит (и не должно, как в вашем первом заявлении). И это означает, что выражение "частные методы нельзя переопределить".

Ответ 5

Я думаю, вы неверно истолковываете то, что говорит этот пост. Это не говорит о том, что дочерний класс "ограничен от реализации собственного метода с тем же именем/сигнатурой".

Здесь код, слегка отредактированный:

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. Однако, поскольку метод определен как закрытый, компилятор фактически рассматривает тип переменной, а не тип объекта. И он переводит вызов метода в invokespecial (я думаю, что правый код операции, не проверял спецификацию JVM), а не invokeinstance.

Ответ 6

Кажется, это вопрос выбора и определения. Причина, по которой вы не можете сделать это в java, состоит в том, что спецификация говорит так, но вопрос был более чем объясняется в спецификации.

Тот факт, что С++ позволяет это (даже если мы используем виртуальное ключевое слово для принудительной динамической отправки), показывает, что нет причин, по которым вы не могли этого позволить.

Однако для заменить метод:

class B {
    private int foo() 
    {
        return 42;
    }

    public int bar()
    {
        return foo();
    }
}

class D extends B {
    private int foo()
    {
        return 43;
    }

    public int frob()
    {
        return foo();
    }
}

Кажется, компилировать ОК (в моем компиляторе), но D.foo не связан с B.foo(т.е. он не переопределяет его) - bar() всегда возвращает 42 (вызывая B.foo) и frob() всегда возвращает 43 (путем вызова D.foo) независимо от того, вызван ли он экземпляром B или D.

Одна из причин, по которой Java не разрешает переопределять метод, заключается в том, что они не хотели, чтобы метод был изменен, как в примере Konrad Rudolph. Обратите внимание, что С++ здесь отличается тем, что вам нужно использовать ключевое слово "virtual", чтобы получить динамическую отправку. По умолчанию у него нет так, что вы не можете модифицировать код в базовом классе, который использует метод hasCredentials. Вышеприведенный пример также защищает от этого, поскольку D.foo не заменяет вызовы foo из B.

Ответ 7

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

Ответ 8

Приносим извинения за неправильное использование термина и неправильное его описание. В моем описании описывается сценарий. Следующий код расширяет пример Jon Skeet для отображения моего сценария:

class Base {
   public void callFoo() {
     foo();
   }
   private void foo() {
   }
}

class Child extends Base {
    private void foo() {
    }
}

Использование выглядит следующим образом:

Child c = new Child();
c.callFoo();

Проблема, с которой я столкнулся, заключается в том, что вызывается метод parent foo(), хотя, как показывает код, я вызывал callFoo() в переменной экземпляра дочернего элемента. Я думал, что я определяю новый частный метод foo() в Child(), который вызвал бы метод наследуемого метода callFoo(), но я думаю, что часть того, что сказал kdgregory, может относиться к моему сценарию - возможно, из-за того, как конструктор производного класса вызывает super(), или, возможно, нет.

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

Ответ 9

Помимо всего, что было сказано ранее, существует очень семантическая причина того, что нельзя отключать частные методы... ОНИ ЧАСТНЫЕ!!!

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

Ответ 10

Класс определяется тем, какие методы он предоставляет и как они ведут себя. Не то, как они реализованы внутри страны (например, путем вызова частных методов).

Поскольку инкапсуляция связана с поведением, а не с деталями реализации, частные методы не имеют ничего общего с инкапсуляцией идеи. В некотором смысле, ваш вопрос не имеет смысла. Ему нравится спрашивать: "Как ставить крем в кофе не нарушением инкапсуляции?"

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