неизменный класс должен быть окончательным? - PullRequest
20 голосов
/ 28 сентября 2008

В статье говорится , что:

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

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

Ответы [ 6 ]

11 голосов
/ 26 сентября 2012

Объяснение этому дано в книге «Эффективная 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;

   }

Если вы разрешите подклассификацию, это может нарушить «чистоту» неизменного объекта.

10 голосов
/ 28 сентября 2008

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

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

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

9 голосов
/ 28 сентября 2008

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

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

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

6 голосов
/ 28 сентября 2008

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

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

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

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

И вот что они поместили бы в метод DoRequest (URL-адрес MyUrlClass):

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

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

Кстати, я ничего не имею против evilhackers.org, если он вообще существует ...

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

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

0 голосов
/ 27 мая 2018

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

Как указано в oracle docs , в основном есть 4 шага, чтобы сделать класс неизменным.

Итак, один из пунктов гласит, что

для создания класса Неизменяемый класс должен быть помечен как окончательный или иметь закрытый конструктор

Ниже приведены 4 шага, чтобы сделать класс неизменным (прямо из документов оракула)

  1. Не предоставляйте методы-установщики - методы, которые изменяют поля или объекты, на которые ссылаются поля.

  2. Сделайте все поля окончательными и приватными.

  3. Не разрешать подклассам переопределять методы. Самый простой способ сделать это - объявить класс как final. Более сложный подход - сделать конструктор частным и создать экземпляры в фабричных методах.

  4. Если поля экземпляра содержат ссылки на изменяемые объекты, не разрешайте изменять эти объекты:

    • Не предоставлять методы, которые изменяют изменяемые объекты.
    • Не делиться ссылками на изменяемые объекты. Никогда не храните ссылки на внешние изменяемые объекты, переданные в конструктор; при необходимости создайте копии и сохраните ссылки на копии. Аналогично, при необходимости создайте копии ваших внутренних изменяемых объектов, чтобы избежать возврата оригиналов в ваши методы.
0 голосов
/ 27 января 2010

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

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

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

...