Java-модификаторы доступа и методы переопределения
Почему Java указывает, что спецификатор доступа для переопределенного метода может разрешить больший, но не меньший доступ, чем переопределенный метод? Например, метод защищенного экземпляра в суперклассе может быть сделан открытым, но не приватным, в подклассе.
7 ответов
Это фундаментальный принцип в ООП: дочерний класс является полноценным экземпляром родительского класса и поэтому должен представлять как минимум тот же интерфейс, что и родительский класс. Делать защищенные / общественные вещи менее заметными нарушит эту идею; Вы можете сделать дочерние классы непригодными в качестве экземпляров родительского класса.
Представьте себе эти два класса:
public class Animal {
public String getName() { return this.name; }
}
public class Lion extends Animal {
private String getName() { return this.name; }
}
Я мог бы написать этот код:
Animal lion = new Lion();
System.out.println( lion.getName() );
И это должно было бы быть действительным, так как в Animal метод getName() является общедоступным, даже если он был закрытым для Lion. Таким образом, невозможно сделать вещи менее заметными на подклассах, поскольку, получив ссылку на суперкласс, вы сможете получить доступ к этому материалу.
Возьмите пример, приведенный ниже
class Person{
public void display(){
//some operation
}
}
class Employee extends Person{
private void display(){
//some operation
}
}
Типичное переопределение происходит в следующем случае
Person p=new Employee();
Вот p
является ссылкой на объект с типом Person(суперкласс), когда мы вызываем p.display (). Поскольку модификатор доступа более ограничен, ссылка на объект p
не может получить доступ к дочернему объекту типа Employee
Потому что это было бы странно
class A {
public void blah() {}
}
class B extends A {
private void blah() {}
}
B b = new B();
A a = b;
b.blah(); // Can't do it!
a.blah(); // Can do it, even though it's the same object!
Поздно к вечеринке, но я хотел бы добавить еще одну проблему, связанную с переопределением: метод переопределения должен разрешать меньшее (или тот же уровень) выбрасываемое исключение, чем переопределенный метод; даже ничего не бросаемо вообще.
Принцип замещения Лискова может объяснить это тоже:
interface Actionable {
void action() throws DislocationException;
}
public class Actor implements Actionable {
@Override
public void action() throws DislocationException {
//....
}
}
public class Stuntman implements Actionable {
@Override // this will cause compiler error
public void action() throws DislocationException, DeathException {
//....
}
}
// legacy code to use Actionable
try {
Actionable actor = new Actor(); // this cannot be replaced by a Stuntman,
// or it may break the try/catch block
actor.action();
} catch (DislocationException exc) {
// Do something else
}
Выше, переопределенный метод взял на себя обязательство, что в худшем случае он выдаст исключение DislocationException, больше не требуется (требуется врач в месте съемки). Таким образом, переопределяющий метод не должен нарушать это, добавляя больше DeathException (или необходима скорая помощь)
Я часто называю главное правило: "[может быть] больше доступа [уровень], [но] меньше исключений"
Поскольку подкласс является специализацией суперкласса, или, другими словами, это расширение суперкласса.
Представьте себе, например, метод toString. Все объекты Java имеют его, потому что он есть у класса Object. Представьте, что вы можете определить класс с помощью метода toString private. Вы больше не будете относиться ко всем объектам одинаково. Например, вы больше не сможете безопасно это сделать:
for (Object obj : collection) System.out.println(obj);
Перефразируя то, что уже было сказано, это связано с тем, как Java компилируется в байт-код, который затем интерпретируется JVM. когда дочерний класс переопределяет один из своих родительских методов, компилятор использует ссылочный тип, чтобы определить, какой из двух методов использовать. Затем JVM использует тип объекта во время выполнения, чтобы определить, какой метод действительно следует использовать.
В приведенном выше примере; Animal lion = new Lion()
, когда lion.getName()
вызывается, компилятор использует версию метода Animal, и JVM заменяет ее / может заменить ее версией Lion, потому что она идеально "подходит". Но если бы Lion было разрешено ограничивать getName() больше, чем ограничение для Animal getName(), вы могли бы обойти ограничение, потому что компилятор будет рассматривать его как неограниченный, если объект Lion имеет ссылку Animal.
Чтобы решить эту проблему, java запрещает дочернему элементу делать переопределенный метод более ограниченным, чем метод, который он переопределяет.
Что касается конкретного случая, который вы упомянули, как именно Java справится с этим? Если подкласс сделал открытый / защищенный метод частным, то что должна делать JVM, когда этот метод вызывается для экземпляра подкласса? Почитать приват и вызвать реализацию суперкласса? Кроме того, вы нарушаете контракт, указанный суперклассом, когда неожиданно говорите, что "никто не может получить доступ к этому методу, несмотря на то, что изначально было сказано в контракте".