Скрывает ли up приведение в Java методы и поля подкласса?
Вопрос
В программе, которую я пишу, у меня есть класс RestrictedUser
и класс User
это производное от RestrictedUser.
Я пытаюсь скрыть пользовательские методы, приведя к RestrictedUser
но когда я выполняю кастинг, пользовательские методы все еще доступны.Кроме того, когда я запускаю отладчик, тип переменной отображается как User
.
RestrictedUser restricted = regularUser;
Скрывает ли up приведение в Java методы и поля подкласса или я делаю что-то неправильно?Есть ли обходной путь?
Спасибо
Решение
Если бы вы попытались запустить этот код:
User user = new User(...);
RestrictedUser restricted = user;
restricted.methodDefinedInTheUserClass(); // <--
вы бы получили сообщение об ошибке компиляции.Это не будет безопасно ни в каком виде, потому что даже если бы вы передали RestrictedUser
чтобы, скажем, другой метод, этот метод мог бы сделать это:
if (restricted instanceof User) {
User realUser = (User)restricted;
realUser.methodDefinedInTheUserClass(); // !!
}
и ваш "ограниченный" пользователь больше не так ограничен.
Объект отображается в отладчике как User
объект, потому что это как User
объект, даже если ссылка на объект хранится в RestrictedUser
переменная.По сути, помещение экземпляра дочернего класса в переменную с типом родительского класса действительно "скроет" методы и поля подкласса, но ненадежно.
Другие советы
Я не уверен точно, о чем вы спрашиваете, поскольку терминология, которую вы используете, немного неясна, но вот что.Если у вас есть суперкласс и подкласс, вы можете скрыть методы в суперклассе от подкласса, сделав их закрытыми.Если вам нужно, чтобы общедоступные методы в суперклассе были невидимыми, вам не повезло.Если вы приведете подкласс к суперклассу, то общедоступные методы в подклассе больше не будут видны.
Одно из предложений, которое могло бы вам помочь, заключалось бы в использовании композиции, а не наследования, извлечении чувствительных методов в отдельный класс, а затем вставке соответствующего экземпляра этого класса в объект по мере необходимости.Вы можете делегировать методы из класса введенному классу, если вам нужно.
Вы путаете статический тип и динамический тип.
Статический тип - это тип ссылки.Когда вы выполняете преобразование из Производного в базовое, вы сообщаете компилятору, что, насколько вам и ему известно, объект, на который указано, является базовым.То есть вы обещаете, что объект, на который указано, является либо null, либо Base, либо чем-то производным от Base.То есть, у него есть общедоступный интерфейс Base.
После этого вы не сможете вызывать методы, объявленные в Derived (а не в Base), но при вызове любых базовых методов, переопределенных Derived, вы получите переопределенную версию Derived .
Если вам нужно защитить подкласс, вы можете использовать шаблон делегирования:
public class Protect extends Superclass { // better implement an interface here
private final Subclass subclass;
public Protect(Subclass subclass) {
this.subclass = subclass;
}
public void openMethod1(args) {
this.subclass.openMethod1(args);
}
public int openMethod2(args) {
return this.subclass.openMethod2(args);
}
...
}
Вы также можете подумать об использовании java.lang.reflect.Прокси
[]]
Ответ Питера и Ответ Джона являются правильными:простое приведение статического типа ничего не даст, если реальный тип объекта (к которому клиентский код может приводить, вызывать методы отражения и т.д.) Все еще доступен.Вы должны каким-то образом замаскировать реальный тип (как упоминается в ответе Питера).
На самом деле существует шаблон для этого:взгляните на unconfigurable*
методы в Executors
класс, если у вас есть доступ к исходному коду JDK.
Java - Язык
В Java вы никогда не сможете "Скрыть" что-то от объекта.Компилятор может забыть то, что вы знаете в деталях о конкретном экземпляре, который у вас есть.(например, какой это подкласс) Отладчик знает гораздо больше, чем компилятор, поэтому он сообщит вам, к какому фактическому типу относится текущий экземпляр, что, вероятно, и является тем, с чем вы сталкиваетесь.
ООП
Похоже, вы пишете логику, которая должна знать, какой тип объекта вы используете, что не рекомендуется в ООП, но часто требуется по практическим соображениям.
Ограничивающие Прилагательные
Как я уже сказал в комментарии к вопросу, вам должно быть ясно, что здесь является базовым классом.Интуитивно ограниченный пользователь должен быть подклассом User, поскольку название предполагает более специализированный тип.(Имя с дополнительным активным прилагательным.) В вашем случае это особенное, поскольку это ограничивающее прилагательное, которое позволило бы вам полностью поместить User в подкласс RestrictedUser.Я бы рекомендовал переименовать их во что-то вроде:BasicUser /UserWithId и NonRestrictedUser, чтобы избежать ограничивающего прилагательного, которое здесь сбивает с толку.
Кроме того, не поддавайтесь искушению думать, что вы также можете скрыть методы в анонимном классе.Например, это не обеспечит той конфиденциальности, на которую вы рассчитываете:
Runnable run = new Runnable() {
@Override
public void run() {
System.out.println("Hello, world!");
}
public void secretSquirrel() {
System.out.println("Surprise!");
}
}
Возможно, вы не сможете назвать реальный тип run
(для того, чтобы привести к нему напрямую), но вы все равно можете получить его класс с getClass()
, и вы можете позвонить secretSquirrel
используя отражение.(Даже если secretSquirrel
является частным, если у вас нет SecurityManager
, или, если он был настроен для обеспечения доступности, отражение также может вызывать частные методы.)
Я думаю, вы, вероятно, сделали неудачный выбор названия:Я бы подумал RestrictedUser
был бы подклассом User
, а не наоборот, поэтому я буду использовать UnrestrictedUser
как подкласс.
Если вы поднимаете UnrestrictedUser
к a RestrictedUser
, ваша ссылка сможет получить доступ только к методам , объявленным в RestrictedUser
.Однако, если вы опустите его обратно на UnrestrictedUser
, у вас будет доступ ко всем методам.Поскольку объекты Java знают, к какому типу они относятся на самом деле, все, что на самом деле является UnrestrictedUser
всегда можно вернуться к нему, независимо от того, какой тип ссылки вы используете (даже Object
).Здесь неизбежна и часть языка.Тем не менее, вы могли бы скрыть это за каким-нибудь прокси-сервером, но в вашем случае это, вероятно, противоречит цели.
Кроме того, в Java все нестатические методы по умолчанию являются виртуальными.Это означает , что если UnrestrictedUser
переопределяет некоторый метод, объявленный в RestrictedUser
, тогда UnrestrictedUser
версия будет использоваться, даже если вы получите к ней доступ через RestrictedUser
ссылка.Если вам нужно вызвать версию родительского класса, вы можете выполнить невиртуальный вызов, вызвав RestrictedUser.variableName.someMethod()
.Однако вам, вероятно, не следует использовать это очень часто (наименьшая из причин заключается в том, что это нарушает инкапсуляцию).
Я также думаю, что этот вопрос напрашивается на другой вопрос.Как вы планируете вызывать этот метод?Похоже, что наличие иерархии классов, где подклассы вводят новые сигнатуры методов, потребует проверки instanceof в вызывающих устройствах.
Можете ли вы обновить свой вопрос, включив в него сценарий, в котором вы бы вызывали эти методы, о которых идет речь?Возможно, мы сможем помочь больше.
Технический ответ был дан Джоном Калсбиком.Я хочу подумать о проблеме дизайна.То, что вы пытаетесь сделать, это запретить какому-либо сегменту кода или некоторым разработчикам что-либо знать о классе User.Вы хотите, чтобы они относились ко всем Пользователям так, как если бы они были ограниченными пользователями.
То, как вы могли бы это сделать, связано с разрешениями.Вам нужно запретить разработчикам, о которых идет речь, иметь доступ к классу User.Вероятно, вы можете сделать это, поместив его в пакет и ограничив доступ к пакету.