2016-03-12 1 views
21

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

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

+0

Вы можете сделать переопределенный окончательный, но не закрытый. Кроме того, вы можете сделать это устаревшим и отказаться от неподдерживаемого исключения операции (guava делает это для неизменных коллекций) –

ответ

12

Это можно расслабиться ограничение, но не сделать его более ограничительный характер:

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

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

+0

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

+0

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

+1

В вашем примере кода не будет экземпляром класса 'C' still * иметь * a' method() 'из класса' B'? Поскольку 'C расширяет B'. Добавление 'implements WithoutMethod' для класса' C' не может *** удалить ** 'method()' из класса 'C' *. Правильно? Итак, «* работает только с инстансами через интерфейсы». «Просто давайте вам ** притворяться **, что' C' не имеет 'method()'? – Tersosauros

3

Ниже приведена часть договора @Override.

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

The access level cannot be more restrictive than the overridden method's access level. For example: if the superclass method is declared public then the overridding method in the sub class cannot be either private or protected.

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

8

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

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 «s абстракции подразумевает, что есть не-частного 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()"); 
    } 
} 
+1

, если я сделаю метод в A защищенном и в C защищенном, а также он будет работать, и доступ извне пакета или подкласса не будет возможным ? – jam

+0

Да, но он хочет, чтобы он был закрыт. –

3

ТЕОРИЯ:

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

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

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

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

ПРАКТИКА:

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

+4

Это неверно, хотя это распространенный миф. Фактически, правильный порядок «public» -> protected -> default-access -> private'. Фактически вы можете получить доступ к защищенным членам из других классов в одном и том же пакете * и * из подклассов в других пакетах, в то время как доступ по умолчанию обеспечивает только первый. См. [JLS 8.4.8.3] (https://docs.oracle.com/javase/specs/jls/se8/html/jls-8.html#jls-8.4.8.3). –

+0

@SergeyTachenov, вы правы, я исправил – Andrew

6

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

В основном говорит: класс S является подклассом другого класса T только тогда, когда вы можете заменить любое появление какого-либо «объекта T» каким-либо «объектом S» - не заметив.

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

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

+0

LSP требует, чтобы методы базового класса существовали и имели определенную функцию во всех производных классах. Это не требует, чтобы функция действительно была * полезной * во всех производных классах. Например, было бы законным, если бы контракт производного класса указывал, что какое-либо свойство виртуального базового класса всегда будет возвращать «false» для всех экземпляров этого производного класса. В таких случаях вполне возможно скрыть этот член в производном классе, так как не было бы смысла ссылаться на ссылку *, известную * для идентификации объекта производного класса. – supercat

+0

Вы сами сказали: метод должен существовать в производном классе. Превращение его в частное - это то же самое, что полностью удалить из внешней перспективы. И, конечно же, вы можете делать с «S», что позволяет S; но в этом весь смысл «прозрачного обмена»; вы получаете доступ к S как T. Таким образом, я не уверен, что вы пытаетесь сказать. – GhostCat

+0

Если получатели ссылки на экземпляр какого-либо класса могут захотеть использовать свой метод «Wizzle()», если он есть, или выполнить некоторую другую последовательность методов, которые могут достичь такого же эффекта, но менее эффективно, а некоторые производные будут способный к «Wizzle()», а другие - нет, может быть полезно, чтобы базовый класс включал свойство «CanWizzle()» и «Wizzle()», при этом контракт заключался в том, что если «CanWizzle()» возвращает true метод «Wizzle()» вернет что-то полезное, и если «CanWizzle()» возвращает false, это может и не быть. Производный класс, который не может «Wizzle()» ... – supercat

4

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

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

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

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

Редактировать

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

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

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