Может ли переопределяющий метод иметь спецификатор доступа, отличный от описанного в базовом классе?
Какой модификатор доступа в абстрактном классе я должен использовать для метода, чтобы подклассы могли решить, должен ли он быть публичным или нет? Можно ли "переопределить" модификатор в Java или нет?
public abstract class A {
??? void method();
}
public class B extends A {
@Override
public void method(){
// TODO
}
}
public class C extends B {
@Override
private void method(){
// TODO
}
}
Я знаю, что будет проблема со статическим связыванием, если кто-то звонит:
// Will work
A foo = new B()
foo.method();
// Compiler ?
A foo = new C();
foo.method();
Но, может быть, есть другой способ. Как мне этого добиться?
6 ответов
Можно ослабить ограничение, но не сделать его более ограничительным:
public abstract class A {
protected void method();
}
public class B extends A {
@Override
public void method(){ // OK
}
}
public class C extends A {
@Override
private void method(){ // not allowed
}
}
Создание оригинального метода private
тоже не будет работать, так как такой метод не виден в подклассах и поэтому не может быть переопределен.
Я бы порекомендовал использовать interface
s выборочно выставить или скрыть метод:
public interface WithMethod {
// other methods
void method();
}
public interface WithoutMethod {
// other methods
// no 'method()'
}
public abstract class A {
protected void method();
}
public class B extends A implements WithMethod {
@Override
public void method(){
//TODO
}
}
public class C extends B implements WithoutMethod {
// no 'method()'
}
... тогда работать только с экземплярами через интерфейсы.
При переопределении методов вы можете изменить только модификатор на более широкий, а не наоборот. Например, этот код будет действительным:
public abstract class A {
protected void method();
}
public class B extends A {
@Override
public void method() { }
}
Однако, если вы попытаетесь сузить видимость, вы получите ошибку во время компиляции:
public abstract class A {
protected void method();
}
public class B extends A {
@Override
private void method() {}
}
Для вашего случая я бы предложил сделать C
не реализует A
, как A
Абстракция подразумевает, что есть не частный method()
:
public class C {
private void method(){
//TODO
}
}
Другой вариант - сделать method()
реализация в C
бросая RuntimeException:
public class C extends A {
@Override
public void method(){
throw new UnsupportedOperationException("C doesn't support callbacks to method()");
}
}
То, о чем вы просите, невозможно по очень веским причинам.
Принцип подстановки Лискова в основном гласит: класс S является подклассом другого класса T только тогда, когда вы можете заменить любое вхождение некоторого "объекта T" некоторым "объектом S" - не замечая этого.
Если вы позволите, чтобы S сводил открытый метод к частному, то вы больше не можете этого делать. Потому что внезапно этот метод, который может быть вызван при использовании некоторого T..., больше не доступен для вызова в S.
Короче говоря: наследство не то, что просто падает с неба. Это свойство классов, за которые вы как программист несете ответственность. Другими словами: наследование означает больше, чем просто запись "класс S расширяет T" в вашем исходном коде!
Это невозможно из-за полиморфизма. Учтите следующее. У вас есть метод в классе A
с некоторым модификатором доступа, который не private
, Почему не частный? Потому что, если бы он был частным, никакой другой класс не мог бы даже знать о его существовании. Так что это должно быть что-то еще, и это что-то еще должно быть доступно откуда- то.
Теперь давайте предположим, что вы передаете экземпляр класса C
куда-то Но вы прогнали его A
заранее, и поэтому вы получите этот код где-то:
void somewhereMethod(A instance) {
instance.method(); // Ouch! Calling a private method on class C.
}
Один хороший пример того, как это сломалось - это QSaveFile в Qt. В отличие от Java, C++ на самом деле позволяет снизить права доступа. Так они и сделали, запретив close()
метод. То, что они в конечном итоге это QIODevice
подкласс, который на самом деле не QIODevice
больше Если вы передаете указатель на QSaveFile
в какой-то метод принятия QIODevice*
, они все еще могут позвонить close()
потому что это публично в QIODevice
, Они "исправили" это, сделав QSaveFile::close()
(который является частным) вызов abort()
, так что, если вы делаете что-то подобное, ваша программа немедленно вылетает. Не очень хорошее "решение", но лучшего нет. И это просто пример плохого ОО дизайна. Вот почему Java не позволяет этого.
редактировать
Не то чтобы я пропустил, что твой класс абстрактный, но я также упустил тот факт, что B extends C
не A
, Таким образом, то, что вы хотите сделать, совершенно невозможно. Если метод public
в B он также будет публичным во всех подклассах. Единственное, что вы можете сделать, это документировать, что он не должен вызываться, и, возможно, переопределить его, чтобы UnsupportedOperationException
, Но это приведет к тем же проблемам, что и с QSaveFile
, Помните, что пользователи вашего класса могут даже не знать, что это экземпляр C
так что у них даже не будет возможности прочитать его документацию.
В целом, это просто очень плохая идея. Возможно, вам следует задать еще один вопрос о конкретной проблеме, которую вы пытаетесь решить с помощью этой иерархии, и, возможно, вы получите несколько полезных советов о том, как сделать это правильно.
Вот часть @Override
контракт.
Ответ: нет никакой возможности достичь того, что у вас есть.
Уровень доступа не может быть более ограничительным, чем уровень доступа переопределенного метода. Например: если метод суперкласса объявлен как открытый, то метод переопределения в подклассе не может быть ни частным, ни защищенным.
Это не проблема, касающаяся abstract
только классы, но все классы и методы.
ТЕОРИЯ:
У вас есть определенный порядок модификаторов:
public <- protected <- default-access X<- private
Когда вы переопределяете метод, вы можете увеличивать, но не уменьшать уровень модификатора. Например,
public -> []
protected -> [public]
default-access -> [public, default-access]
private -> []
ПРАКТИКА:
В вашем случае вы не можете включить ???
в какой-то модификатор, потому что private
самый низкий модификатор и private
Члены класса не наследуются.