Фон
Есть три места, где final может отображаться на Java:
Завершение класса предотвращает все подклассы класса. Создание метода final не позволяет подклассам метода переопределять его. Создание поля final предотвращает его изменение позже.
заблуждения
Существуют оптимизации, которые происходят вокруг окончательных методов и полей.
Последний метод облегчает оптимизацию HotSpot с помощью встраивания. Тем не менее, HotSpot делает это, даже если метод не является окончательным, поскольку он работает при условии, что он не был переопределен, пока не доказано обратное.Подробнее об этом на SO
Последняя переменная может быть агрессивно оптимизирована, и больше об этом можно прочитать в разделе 17.5.3 JLS .
Однако с этим пониманием следует помнить, что ни одна из этих оптимизаций не предназначена для того, чтобы сделать финал класса . Нет никакого выигрыша в производительности, сделав финал класса.
Последний аспект класса также не имеет ничего общего с неизменяемостью. Можно иметь неизменный класс (такой как 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? для больше о неизменной природе Струн.
Нечестивые строки
Есть ряд различных гнусных опций для строк. Подумайте, сделал ли я String, которая всегда возвращала true, когда вызывался равен ... и передал это в проверку пароля? Или сделал так, чтобы назначения MyString отправляли копию строки на какой-либо адрес электронной почты?
Это очень реальные возможности, когда у вас есть возможность подкласс String.
Java.lang Строковые оптимизации
Хотя раньше я упоминал, что final не делает String быстрее. Однако класс String (и другие классы в нем java.lang
) часто используют защиту полей и методов на уровне пакета, чтобы другие java.lang
классы могли связываться с внутренними объектами, вместо того чтобы постоянно проходить через открытый API для String. Такие функции, как getChars без проверки диапазона или lastIndexOf, который используется StringBuffer, или конструктор, который разделяет базовый массив (обратите внимание, что это вещь Java 6, которая была изменена из-за проблем с памятью).
Если бы кто-то создал подкласс String, он не смог бы поделиться этими оптимизациями (если только он не был частью этого java.lang
, но это запечатанный пакет ).
Сложнее создать что-то для расширения
Создать что-то, что можно расширить, сложно . Это означает, что вы должны выставлять части своих внутренних элементов, чтобы что-то еще можно было модифицировать.
Расширяемая строка не могла исправить утечку памяти . Эти части должны были бы быть подвержены подклассам, и изменение этого кода означало бы, что подклассы сломались бы.
Java гордится обратной совместимостью и, открывая базовые классы для расширения, человек теряет часть этой способности исправлять вещи, сохраняя при этом совместимость с подклассами сторонних производителей.
У Checkstyle есть правило, которое оно применяет (которое действительно расстраивает меня при написании внутреннего кода), называемое «DesignForExtension», которое обеспечивает, чтобы каждый класс был:
- абстрактный
- окончательный
- Пустая реализация
Рациональным для чего является:
Этот стиль разработки API защищает суперклассы от взлома подклассами. Недостатком является то, что подклассы ограничены в своей гибкости, в частности, они не могут помешать выполнению кода в суперклассе, но это также означает, что подклассы не могут испортить состояние суперкласса, забыв вызвать метод super.
Разрешение расширенных классов реализации означает, что подклассы могут повредить состояние класса, на котором он основан, и сделать так, чтобы различные гарантии, которые дает суперкласс, были недействительными. Для чего-то такого сложного, как String, почти наверняка, что изменение его части что- то сломает.
Разработчик хурбис
Это часть того, чтобы быть разработчиком. Рассмотрим вероятность того, что каждый разработчик создаст свой собственный подкласс 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 делает со строками. Это облегчает работу программиста, но вносит некоторые несоответствия в язык. Разрешение подклассов на 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 )
Все объекты могут быть преобразованы в строки, просто используя их в контексте строки.
Строковый интернинг ( JavaDoc )
Класс String имеет доступ к пулу Strings, что позволяет ему иметь канонические представления объекта, который заполняется в типе компиляции литералами String.
Разрешение подкласса String будет означать, что эти биты со String, которые облегчают программирование, станут очень трудными или невозможными, когда возможны другие типы String.