Скрывает ли up приведение в Java методы и поля подкласса?

StackOverflow https://stackoverflow.com/questions/712528

  •  23-08-2019
  •  | 
  •  

Вопрос

В программе, которую я пишу, у меня есть класс 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.Вероятно, вы можете сделать это, поместив его в пакет и ограничив доступ к пакету.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top