Скрывает ли приведение в Java методы и поля подкласса? - PullRequest
8 голосов
/ 03 апреля 2009

В программе, которую я пишу, у меня есть класс RestrictedUser и класс User, производный от RestrictedUser. Я пытаюсь скрыть пользовательские методы, приводя к RestrictedUser, но когда я делаю Приведение пользовательских методов все еще доступно. Также, когда я запускаю отладчик, тип переменной появляется как User.

RestrictedUser restricted = regularUser;

Скрывает ли приведение в Java методы и поля подкласса или я что-то не так делаю? Есть ли обходной путь?

Спасибо

Ответы [ 10 ]

16 голосов
/ 03 апреля 2009

Если вы попытаетесь запустить этот код:

User user = new User(...);
RestrictedUser restricted = user;
restricted.methodDefinedInTheUserClass(); // <--

вы получите ошибку компиляции. Это не будет надежно ни в какой форме, ни в форме, ни в форме, потому что даже если бы вы передали RestrictedUser, скажем, другому методу, этот метод мог бы сделать это:

if (restricted instanceof User) {
    User realUser = (User)restricted;
    realUser.methodDefinedInTheUserClass(); // !!
}

и ваш «ограниченный» пользователь больше не ограничен.

Объект отображается в отладчике как объект User, поскольку он является объектом User, даже если ссылка на объект хранится в переменной RestrictedUser. По сути, помещение экземпляра дочернего класса в переменную с типом родительского класса действительно «скрывает» методы и поля подкласса, но не безопасно.

3 голосов
/ 03 апреля 2009

Если вам нужно защитить подкласс, вы можете использовать шаблон Delegate:

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.Proxy
[]]

3 голосов
/ 03 апреля 2009

Вы путаете статический тип и динамический тип.

Статический тип - это тип ссылки. Когда вы преобразуетесь из Derived в Base, вы говорите компилятору, что, насколько вам известно, указывается на Base. То есть вы обещаете, что указанный объект является либо нулевым, либо базовым, либо чем-то производным от базового. То есть он имеет открытый интерфейс Base.

После этого вы не сможете вызывать методы, объявленные в Derived (а не в Base), но при вызове любых методов Base, переопределенных Derived, вы получите переопределенную версию Derived.

3 голосов
/ 03 апреля 2009

Я не уверен, что именно вы спрашиваете, поскольку терминология, которую вы используете, немного неясна, но здесь идет речь. Если у вас есть суперкласс и подкласс, вы можете скрыть методы в суперклассе от подкласса, сделав их закрытыми. Если вам нужны открытые методы суперкласса, чтобы быть невидимыми, вам не повезло. Если вы приведете подкласс к суперклассу, то публичные методы подкласса больше не будут видны.

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

2 голосов
/ 03 апреля 2009

Java

В Java вы никогда не сможете «спрятать» что-либо от объекта. Компилятор может забыть то, что вы подробно знаете о конкретном экземпляре, который у вас есть. (например, какой это подкласс) Отладчик знает гораздо больше, чем компилятор, поэтому он скажет вам, какой фактический тип является текущим экземпляром, что, вероятно, вы испытываете.

OOP

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

Ограничение прилагательных

Как я сказал в комментарии к вопросу, вам должно быть понятно, что здесь является базовым классом. Интуитивно RestrictedUser должен быть подклассом пользователя, поскольку имя предлагает более специализированный тип. (Наименование имеет дополнительное активное прилагательное.) В вашем случае это особенное, так как это ограничивающее прилагательное, которое позволит вам поместить пользователя в качестве подкласса RestrictedUser. Я бы порекомендовал переименовать их в что-то вроде: BasicUser / UserWithId и NonRestrictedUser, чтобы избежать ограничительного прилагательного, которое является здесь запутанной частью.

2 голосов
/ 03 апреля 2009

ответ Питера и ответ Джона верны: простое приведение статического типа ничего не даст, если реальный тип объекта (к которому может привести клиентский код, вызывает методы отражения на и т. д.) все еще доступен. Вы должны как-то замаскировать реальный тип (как упоминает ответ Питера).

На самом деле есть шаблон для этого: посмотрите на unconfigurable* методы в классе Executors, если у вас есть доступ к исходному коду JDK.

1 голос
/ 03 апреля 2009

Кроме того, не поддавайтесь искушению думать, что вы также можете скрыть методы в анонимном классе. Например, это не обеспечит конфиденциальность, которую вы ожидаете:

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, или если он был настроен для обеспечения доступности, отражение может также вызывать закрытые методы.)

0 голосов
/ 03 апреля 2009

Технический ответ дал Джон Калсбек. Я хочу подумать о проблеме дизайна. То, что вы пытаетесь сделать, это помешать некоторым сегментам кода или некоторым разработчикам узнать что-либо о классе User. Вы хотите, чтобы они относились ко всем пользователям, как если бы они были RestrictedUsers.

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

0 голосов
/ 03 апреля 2009

Я также думаю, что этот вопрос напрашивается на другой вопрос. Как вы планируете вызвать этот метод? Кажется, что наличие иерархии классов, в которой подклассы вводят новые сигнатуры методов, потребует проверки экземпляров в вызывающих.

Можете ли вы обновить свой вопрос, включив в него сценарий, в котором вы бы назвали эти методы вопросом? Возможно, мы сможем помочь больше.

0 голосов
/ 03 апреля 2009

Я думаю, вы, вероятно, сделали неправильный выбор имен: я бы подумал, что RestrictedUser будет подклассом User, а не наоборот, поэтому я буду использовать UnrestrictedUser в качестве подкласса.

Если вы повысите UnrestrictedUser до RestrictedUser, ваша ссылка сможет получить доступ только к методам, объявленным в RestrictedUser. Однако, если вы вернете его обратно к UnrestrictedUser, у вас будет доступ ко всем методам. Поскольку объекты Java знают, какой тип они на самом деле, все, что на самом деле является UnrestrictedUser, всегда может быть приведено обратно к нему, независимо от того, какой тип ссылки вы используете (даже Object). Здесь неизбежна и часть языка. Тем не менее, вы можете скрыть это за каким-то прокси, но в вашем случае это, вероятно, побеждает цель.

Кроме того, в Java все нестатические методы являются виртуальными по умолчанию. Это означает, что если UnrestrictedUser переопределяет некоторый метод, объявленный в RestrictedUser, то будет использоваться версия UnrestrictedUser, даже если вы обращаетесь к ней через ссылку RestrictedUser. Если вам нужно вызвать версию родительского класса, вы можете сделать не виртуальный вызов, вызвав RestrictedUser.variableName.someMethod(). Однако вам, вероятно, не следует использовать это очень часто (наименьшая из причин заключается в том, что он нарушает инкапсуляцию).

...