Почему мы не можем назначать более слабые привилегии в подклассе

У меня есть класс, у которого есть метод, спецификатор доступа которого по умолчанию является общедоступным. Теперь я хотел бы расширить этот класс в подклассе, и я хочу переопределить этот метод, чтобы получить спецификатор доступа "private". При компиляции этого кода я получаю ошибку компиляции:

", пытаясь назначить более слабые права доступа".

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

Вот код, вызвавший ошибку компиляции:

class Superclass 
{
    void foo() 
    {
        System.out.println("Superclass.foo");
    }
}

class Subclass extends Superclass 
{
    private void foo() 
    {
        System.out.println("Subclass.foo");
    }
}

Ответ 1

Короткий ответ заключается в том, что он недопустим, поскольку он нарушит подстановку типов; см. также Принцип замещения Лискова (LSP).

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

Например, допустим, что ваш примерный код был законным:

// Assume this code is in some other class ...

SuperClass s1 = new SuperClass();

s1.foo();                          // OK!

SuperClass s2 = new Subclass();

s2.foo();                          // What happens now?

SuperClass s3 = OtherClass.someMethod();

s3.foo();                          // What happens now?

Если вы основываете решение о допустимости s2.foo() для объявленного типа s2, вы разрешаете вызов метода private из-за границы абстракции Subclass.

Если вы основываете решение на фактическом типе объекта, на который ссылается s2, вы не можете сделать проверку доступа статически. Случай s3 делает это еще более ясным. Компилятор не имеет абсолютно никакого способа узнать, каков будет действительный тип объекта, возвращаемого someMethod.

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

Ответ 2

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

SuperClass sc = new SubClass();
sc.foo(); // is package local, not private.

Доступ к sc определяется типом ссылки sc, а не тем, что он ссылается, потому что компилятор не может знать во всех случаях, какой тип объекта находится во время выполнения. Для того чтобы это было безопасное предположение, подкласс должен выполнять контракт, предоставленный родителем, или он не может быть допустимым подклассом. Это ничем не отличается от родителя, говорящего, что метод реализован, но подкласс, говорящий, что он (или не доступен)

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

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

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

Ответ 3

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

Предположим, что это разрешено

class Super {  
    public void method() {  
        System.out.println("Super");  
    }  
}


class Sub extends Super {  
    // This is not allowed, but suppose it was allowed  
    protected void method() {  
        System.out.println("Sub");  
    }  
}

// In another class, in another package:  
Super obj = new Sub();  
obj.method();

obj.method было бы возможно, потому что method() есть public в классе Super. Но это не должно допускаться,  потому что obj действительно ссылается на экземпляр Sub, и в этом классе метод защищен!

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

Ответ 4

Ограничение модификатора доступа метода суперкласса является недопустимым переопределением, поскольку оно нарушает контракт суперкласса и отменяет принцип подстановки, т.е. объект класса суперкласса объекта класса sub-class.

public void doSomething(SuperClass sc) {
  sc.publicMethodInSuperClass();
}

doSomething(new SubClass()); // would break

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

Ссылка:
Принцип подстановки Лискова

Ответ 5

Помимо очевидных проблем с использованием таких конструкций (как указал Питер Лоури в его ответе), читайте также об этой теории: LSP, что означает, что вы должны иметь возможность подставить основной тип своим подклассом.

Ответ 6

Я думаю, что короткий ответ заключается в том, что авторы компилятора установили правила для работы таким образом. LSP не имеет ничего общего с проблемой.

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

Предположим, что вы могли бы написать код, который показал OP. Если у вас есть ссылка на производный класс, вы должны иметь возможность вызвать любых публичных членов производного класса (хотя в этом случае их нет). Однако передайте ссылку как параметр методу, который ссылается на базовый класс, и метод будет ожидать вызова любого общедоступного или пакетного метода, который равен foo. Это LSP, который ищут другие участники!

Пример С++:

class Superclass{
public:
virtual void foo(){ cout << "Superclass.foo" << endl; }
};

class Subclass: public Superclass{
virtual void foo(){ cout << "Subclass.foo" << endl; }
};

int main(){
Superclass s1;
s1.foo() // Prints Superclass.foo

Subclass s2;
// s2.foo();  // Error, would not compile

Superclass& s1a=s2; // Reference to Superclass 'pointing' to Subclass

s1a.foo(); // Compiles OK, Prints Subclass.foo()
}

Ответ 7

При отправке динамического метода вызов переопределенного метода разрешается во время выполнения, а не времени компиляции. Он основан на объекте, на который ссылается во время вызова...

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

Superclass ref=new Subclass();
ref.foo()

Теперь во время выполнения, когда java встречает оператор ref.foo(), ему придется вызывать foo() из Subclass... но метод подкласса foo() объявляется как закрытый в вашем коде, а private can not be вызываемый вне его собственного класса... и теперь есть конфликт, и это приведет к исключению среды выполнения...

Ответ 8

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

правильный порядок: ==> приватный по умолчанию защищенный публичный

* в вашем случае вы идете по умолчанию ==> личное  когда вы идете в неправильном порядке, это приводит к ошибке

Правильный порядок привилегий доступа is--

'class SuperClass{
          void foo(){
    System.out.print("SuperClass");
     }
   class SubClass extends{

  //--default to default--//
         void foo(){
  System.out.print("SubClass");
   }
 //--default to protected--//

  protected void foo(){
  System.out.print("SubClass");

 //--default to public //

 public void foo(){
 System.out.print("SubClass");
 }' 

    **you make sure to preserve correct order while overriding **