Фон
Є три місця, де фінал може відображатися на Java:
Здійснення остаточного класу запобігає всім підкласам класу. Виконання методу остаточним запобігає його перекриття підкласів методу. Здійснення остаточного поля запобігає його подальшому зміні.
Помилкові уявлення
Існують оптимізації, які відбуваються навколо кінцевих методів і полів.
Остаточний метод спрощує HotSpot оптимізувати за допомогою вбудовування. Однак HotSpot робить це, навіть якщо метод не є остаточним, оскільки він працює за припущенням, що його не було відмінено, поки не доведено інше. Більше про це на SO
Кінцева змінна може бути агресивно оптимізована, і докладніше про це можна прочитати в розділі JLS 17.5.3 .
Однак, розуміючи це, слід пам'ятати, що жодна з цих оптимізацій не стосується того, щоб зробити клас остаточним. Немає підвищення продуктивності, зробивши фінал класу.
Заключний аспект класу також не має нічого спільного з незмінністю. Можна мати клас, що не змінюється (наприклад, BigInteger ), який не є остаточним, або клас, який може змінюватися і остаточно (наприклад, StringBuilder ). Рішення про те, чи повинен бути клас остаточним, - це питання дизайну.
Остаточний дизайн
Рядки є одним з найбільш використовуваних типів даних. Вони знаходяться як ключі до карт, вони зберігають імена користувачів та паролі, вони - те, що ви читаєте з клавіатури чи поля на веб-сторінці. Струни є скрізь .
Карти
Перше, що слід врахувати, що станеться, якщо ви зможете підкласировать String - це зрозуміти, що хтось може створити мутаційний клас String, який, здавалося б, інакше є String. Це зіпсує Карти всюди.
Розглянемо цей гіпотетичний код:
Map t = new TreeMap<String, Integer>();
Map h = new HashMap<String, Integer>();
MyString one = new MyString("one");
MyString two = new MyString("two");
t.put(one, 1); h.put(one, 1);
t.put(two, 2); h.put(two, 2);
one.prepend("z");
Це проблема використання мутабельного ключа взагалі з картою, але те, що я намагаюся отримати там, - це раптом ряд речей про розрив карти. Запис більше не в потрібному місці на карті. У HashMap значення хеша змінилося (повинно було) змінитися, і, таким чином, його більше немає в потрібному введенні. У TreeMap дерево зараз зламано, оскільки один із вузлів знаходиться з виворітного боку.
Оскільки використання String для цих клавіш настільки поширене, цю поведінку слід запобігти, зробивши String остаточним.
Можливо, вам буде цікаво прочитати Чому String незмінний на Java? докладніше про непорушний характер Струн.
Підступні струни
Існує ряд різноманітних недобрих варіантів для рядків. Подумайте, чи я створив рядок, який завжди повертався справжнім, коли називались рівними ... і передав це в перевірку паролем? Або зробили так, що завдання на MyString надсилали копію String на якусь електронну адресу?
Це дуже реальні можливості, коли у вас є можливість підкласу String.
Оптимізація рядків Java.lang
Хоча раніше я згадував, що фінал не робить String швидшим. Однак клас String (та інші класи в java.lang
) часто використовують захист полів та методів на рівні пакетів, щоб дозволити іншим java.lang
класам мати можливість попрацювати з внутрішніми, а не постійно проходити через загальнодоступний API для String. Такі функції, як getChars без перевірки діапазону або lastIndexOf, який використовується StringBuffer, або конструктор, який розділяє базовий масив (зауважте, що це Java 6, яку було змінено через проблеми з пам'яттю).
Якщо хтось створив підклас String, він не зміг би поділитися цими оптимізаціями (якщо тільки це не було частиною java.lang
, але це запечатаний пакет ).
Складніше розробити щось для розширення
Важко спроектувати щось, що можна розширити . Це означає, що вам доведеться викрити частини вашого внутрішнього простору для чогось іншого, щоб їх можна було змінити.
Розширювана струна не могла виправити витік пам'яті . Ці частини повинні були бути піддані підкласам, і зміна цього коду означало б, що підкласи будуть порушені.
Java пишається зворотною сумісністю і, відкриваючи основні класи до розширення, втрачає частину цієї здатності виправляти речі, зберігаючи при цьому обчислюваність з підкласами третіх сторін.
У Checkstyle є правило, яке застосовує (що мене дуже засмучує при написанні внутрішнього коду) під назвою "DesignForExtension", яке примушує будь-який клас:
- Анотація
- Фінал
- Порожня реалізація
Раціональне для чого:
Цей стиль дизайну API захищає суперкласи від зламу підкласів. Мінусом є те, що підкласи обмежені у своїй гнучкості, зокрема, вони не можуть запобігти виконанню коду в надкласі, але це також означає, що підкласи не можуть пошкодити стан надкласу, забувши викликати супер-метод.
Дозволити розширення класів реалізації означає, що підкласи можуть пошкодити стан класу, на якому він заснований, і зробити так, що різні гарантії, які дає суперклас, недійсні. Для чого - то так складно , як струни, це майже впевненість , що зміна його частина буде що - небудь зламати.
Хурбі розробника
Його частина бути розробником. Розглянемо ймовірність того, що кожен розробник створить власний підклас String із власною колекцією утилітів . Але тепер ці підкласи не можна вільно присвоювати один одному.
WleaoString foo = new WleaoString("foo");
MichaelTString bar = foo; // This doesn't work.
Цей шлях призводить до божевілля. Передавайте String всюди і перевіряйте, чи є String - це екземпляр вашого класу String чи ні, створивши нову String на основі цього і ... просто, ні. Не варто.
Я впевнений , що ви можете написати хороший клас String , ... але залишити написання кілька реалізацій рядків для тих божевільних людей , які пишуть на C ++ і доводиться мати справу з std::string
і char*
і що - то від підвищення і SString , і все інше .
Java String Magic
Є кілька магічних речей, які Java робить зі Strings. Це полегшує програмісту справу, але вводить деякі невідповідності в мові. Якщо дозволити підкласи на String, було б дуже важливо подумати, як боротися з цими магічними речами:
Строкові літерали (JLS 3.10.5 )
Маючи код, який дозволяє робити:
String foo = "foo";
Це не слід плутати з боксами числових типів, таких як Integer. Ви не можете зробити 1.toString()
, але можете зробити "foo".concat(bar)
.
+
Оператор (ПСБ 15.18.1 )
Жоден інший тип посилання на Java не дозволяє оператору використовувати його. Рядок особливий. Оператор конкатенації рядків також працює на рівні компілятора, так що "foo" + "bar"
стає "foobar"
коли він компілюється, а не під час виконання.
Перетворення рядків (JLS 5.1.11 )
Усі об'єкти можна перетворити в рядки, просто використовуючи їх у String-контексті.
Інструмент для рядків ( JavaDoc )
Клас String має доступ до пулу Strings, які дозволяють йому мати канонічні зображення об'єкта, який заповнюється на тип компіляції з літералами String.
Дозволити підклас String означатиме, що ці біти з String, які полегшують програмування, стали б дуже важкими або неможливими для виконання, коли можливі інші типи String.