2013-07-07 4 views
8

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

"attempting to assign weaker access privileges".

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

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

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

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

Интересный вопрос! Этот самый случай * разрешен в C++. – quamrana

+1

«У меня есть класс, у которого есть метод, спецификатор доступа по умолчанию является общедоступным». Фактически спецификатор доступа по умолчанию не является общедоступным. Если вы не указали какой-либо конкретный спецификатор доступа, то он «по умолчанию» :). – pikrut

+0

@quamrana И что происходит на C++, когда вы присоединяетесь к объекту через переменную типа суперкласса? Компилятор, конечно же, пропустил его ... это ошибка во время выполнения? –

ответ

25

Короткий ответ, что это не допускается, так как это нарушило бы тип взаимозаменяемость; см. также Liskov Substititution Principle (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. Здесь обсуждаемое ограничение языка устраняет эту неприятную проблему.

+1

+1 Правильно, чтобы понять ваш ответ, вам понадобится больше знаний, чем это подразумевается под вопросом. ;) –

+1

@PeterLawrey - Извините ... Мне пришлось помогать с мертвой рыбой. Буквально. –

+0

Нет проблем, + 1'ed это в любом случае. ;) Я имею дело с реактивной задержкой, ее 2:55 здесь. Нужно снова заснуть. –

7

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

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

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

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

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

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

+3

Да, но почему это должно быть (что он спрашивает)? – Bohemian

0

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

+0

+1 LSP - причина. Хорошая ссылка. – Bohemian

1

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

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

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

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

Ссылка:
Liskov substitution principle

5

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

Допустим, что это разрешено

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. Но это не должно быть позволено, , потому что obj действительно ссылается на экземпляр Sub, и в этом классе метод защищен!

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

0

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

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

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

C++ Пример:

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() 
} 
+0

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

+0

Нет, его нет. LSP относится к перспективе клиента, где они знают только о базовом классе и ожидают, что базовый класс будет работать определенным образом. Когда вы поставляете производный класс клиенту, потому что его язык позволяет это, то производный класс должен вести себя одинаково, чтобы функция клиента все еще работала. – quamrana

+0

Я думаю, что вы слишком узко интерпретируете LSP. Определение Liskov & Wing следующее: * «Требование подтипа: пусть φ (x) - свойство, доказуемое относительно объектов x типа T. Тогда φ (y) должно быть истинным для объектов y типа S, где S - подтип T . "*. В этом случае φ имеет доступный метод 'foo'. Это свойство «SuperClass», от которого зависят клиенты, и поэтому также должно быть свойством «SubClass» ... или же экземпляр последнего не подлежит замене для экземпляра первого. См. Https://en.wikipedia.org/wiki/Liskov_substitution_principle. –

0

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

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

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

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

Смежные вопросы