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

Непреложный класс должен быть окончательным?

В в этой статье:

Создание окончательного класса, поскольку оно является неизменным, является хорошей причиной для этого.

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

4b9b3361

Ответ 1

Следуя принципу подстановки Лискова, подкласс может распространяться, но никогда не переопределять контракт его родителя. Если базовый класс неизменен, то трудно найти примеры того, где его функциональность может быть с пользой расширена без нарушения контракта.

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

Я предполагаю, что вы могли бы объявить все поля приватными, а все методы - окончательными, но тогда, что будет использовать наследование?

Ответ 2

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

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

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

Ответ 3

Объяснение этому дано в книге "Эффективная Java"

Рассмотрим классы BigDecimal и BigInteger в Java.

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

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

   public static BigInteger safeInstance(BigInteger val) {

   if (val.getClass() != BigInteger.class)

    return new BigInteger(val.toByteArray());


       return val;

   }

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

Ответ 4

В основном я бы подумал. По той же причине, что String является окончательным, все, что любой код, связанный с безопасностью, хочет рассматривать как непреложный, должен быть окончательным.

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

Теперь у кого-то может возникнуть соблазн написать код диспетчера безопасности, например:

void checkUrl(MyUrlClass testurl) throws SecurityException {
    if (illegalDomains.contains(testurl.getDomain())) throw new SecurityException();
}

И вот что они поместили в свой DoRequest (метод MyUrlClass url):

securitymanager.checkUrl(urltoconnect);
Socket sckt = opensocket(urltoconnect);
sendrequest(sckt);
getresponse(sckt);

Но они не могут этого сделать, потому что вы не сделали финальный финал MyUrlClass. Причина, по которой они не могут этого сделать, заключается в том, что если бы они это сделали, код мог бы избежать ограничений безопасности администратора, просто переопределив getDomain() для возврата "www.google.com" при первом вызове и "www.evilhackers.org", второй, и передать объект своего класса в DoRequest().

У меня нет ничего против evilhackers.org, кстати, если он даже существует...

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

Я не думаю, что статья, на которую вы ссылались, должна восприниматься как инструкция о том, что "все непреложные классы должны быть окончательными", особенно если у вас есть положительная причина для разработки вашего неизменяемого класса для наследования. То, что он говорит, заключается в том, что защита неизменности является веской причиной для финала, где мнимые проблемы с производительностью (о чем он действительно говорит в этой точке) недействительны. Обратите внимание, что он дал "сложный класс, не предназначенный для наследования", как столь же вескую причину. Можно с полным основанием утверждать, что не учитывать унаследованность в ваших сложных классах - это то, чего следует избегать, так же, как неспособность учитывать наследование в ваших неизменных классах. Но если вы не можете объяснить это, вы можете хотя бы оповестить об этом, предупредив его.

Ответ 5

Это хорошая идея сделать класс неизменным по соображениям производительности. Возьмем, например, Integer.valueOf. Когда вы вызываете этот статический метод, ему не нужно возвращать новый экземпляр Integer. Он может вернуть ранее созданный экземпляр в безопасности, зная, что когда он передал вам ссылку на этот экземпляр в последний раз, вы его не модифицировали (я думаю, это тоже хорошая аргументация с точки зрения соображений безопасности).

Я согласен с точкой зрения, сделанной в Effective Java по этим вопросам, - что вы должны либо разрабатывать свои классы для расширяемости, либо сделать их не расширяемыми. Если его намерение сделать что-то расширяемое, возможно, рассмотрим интерфейс или абстрактный класс.

Кроме того, вам не нужно делать класс окончательным. Вы можете сделать конструкторы частными.