Может ли переопределяющий метод иметь спецификатор доступа, отличный от описанного в базовом классе?

Какой модификатор доступа в абстрактном классе я должен использовать для метода, чтобы подклассы могли решить, должен ли он быть публичным или нет? Можно ли "переопределить" модификатор в 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 тоже не будет работать, так как такой метод не виден в подклассах и поэтому не может быть переопределен.

Я бы порекомендовал использовать interfaces выборочно выставить или скрыть метод:

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 Члены класса не наследуются.

Другие вопросы по тегам