Подтвердить что ты не робот

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

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

Но, может быть, есть и другой способ. Как я могу это достичь?

4b9b3361

Ответ 1

Можно ограничить ограничение, но не сделать его более ограничительным:

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 для выборочного поиска или скрытия метода:

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()'
}

... затем работает только с экземплярами через интерфейсы.

Ответ 2

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

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()");
    }
}

Ответ 3

То, о чем вы просите, невозможно по очень веским причинам.

Принцип подстановки Лискова в основном говорит: класс S является подклассом другого класса T только тогда, когда вы можете заменить любое появление какого-либо "объекта T" на некоторый "объект S" - не замечая.

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

Короче говоря: наследование - это не то, что просто выпадает из неба. Это свойство классов , за которое отвечает программист. Другими словами: наследование означает больше, чем просто запись "класса S extends T" в вашем исходном коде!

Ответ 4

Это невозможно из-за полиморфизма. Рассмотрим следующее. У вас есть метод в классе A с некоторым модификатором доступа, который не является private. Почему бы и нет? Потому что, если он был приватным, тогда ни один другой класс не мог даже знать о его существовании. Так что это должно быть что-то еще, и что-то еще должно быть доступно откуда-то.

Теперь предположим, что вы передаете экземпляр класса C где-нибудь. Но вы заранее поднимете его до A, и в результате вы получите код где-то:

void somewhereMethod(A instance) {
    instance.method(); // Ouch! Calling a private method on class C.
}

Один приятный пример того, как это сломалось, - QSaveFile в Qt. В отличие от Java, С++ фактически позволяет снизить права доступа. Поэтому они сделали именно это, запретив метод close(). В итоге у них есть подкласс QIODevice, который больше не является QIODevice. Если вы передадите указатель на QSaveFile для некоторого метода, принимающего QIODevice*, он все равно может вызвать close(), потому что он открыт в QIODevice. Они "исправили" это, сделав QSaveFile::close() (закрытый) вызов abort(), поэтому, если вы сделаете что-то подобное, ваша программа немедленно сработает. Не очень приятное "решение", но лучшего нет. И это всего лишь пример плохого дизайна OO. Это почему Java не позволяет это.

Edit

Не то, чтобы я пропустил, что ваш класс является абстрактным, но я также упустил тот факт, что B extends C, а не A. Таким образом, то, что вы хотите сделать, совершенно невозможно. Если метод public в B, он будет общедоступным и во всех подклассах. Единственное, что вы можете сделать, это документ, который он не должен вызывать и, возможно, переопределить его, чтобы бросить UnsupportedOperationException. Но это привело бы к тем же проблемам, что и к QSaveFile. Помните, что пользователи вашего класса могут даже не знать, что это экземпляр C, поэтому у них даже не будет возможности прочитать его документацию.

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

Ответ 5

Вот часть контракта @Override.

Ответ: нет никакой возможности добиться того, что у вас есть.

Уровень доступа не может быть более ограничительным, чем переопределенный уровень доступа к методу. Например: если метод суперкласса объявленный публичный, тогда метод переопределения в подклассе не может быть либо закрытыми, либо защищенными.

Это не проблема только для классов abstract, а для всех классов и методов.

Ответ 6

Теория:

У вас есть определенный порядок модификаторов:

public <- protected <- default-access X<- private

При переопределении метода вы можете увеличить, но не уменьшить уровень модификатора. Например,

public -> []
protected -> [public]
default-access -> [public, default-access]
private -> []

ПРАКТИКА:

В вашем случае вы не можете превратить ??? в какой-либо модификатор, потому что private является самым низким модификатором, а члены класса private не наследуются.