Я долгое время являюсь Java-разработчиком и, наконец, после специализации у меня есть время, чтобы прилично изучить его, чтобы сдать сертификационный экзамен ... Одна вещь, которая меня всегда беспокоила, это то, что String является "финальной". Я понимаю, когда читаю о проблемах безопасности и связанных с этим вещах ... Но, серьезно, у кого-нибудь есть настоящий пример этого?
Например, что произойдет, если строка не будет финальной? Как будто это не в Ruby. Я не слышал никаких жалоб от сообщества Ruby ... И я знаю о StringUtils и связанных с ним классах, которые вы должны либо реализовать самостоятельно, либо искать в Интернете, чтобы просто реализовать это поведение (4 строки кода), которое вы готовы.
java.io.File
неfinal
. О, это не так. Мудак.Ответы:
Основная причина - скорость:
final
классы не могут быть расширены, что позволило JIT выполнять все виды оптимизаций при обработке строк - никогда не нужно проверять переопределенные методы.Другая причина заключается в безопасности потоков: неизменяемые всегда потокобезопасны, потому что поток должен полностью построить их, прежде чем они могут быть переданы кому-то еще - и после сборки они больше не могут быть изменены.
Кроме того, изобретатели среды выполнения Java всегда хотели ошибиться на стороне безопасности. Возможность расширения String (что я часто делаю в Groovy, потому что это очень удобно) может открыть целую банку червей, если вы не знаете, что делаете.
источник
final
для быстрой проверки того, что метод не переопределяется при компиляции кода.final
ключевого слова в классе. Содержащийся в нем массив символов является окончательным, что делает его неизменным. Завершение самого класса завершает цикл, как упоминает @abl, поскольку изменяемый подкласс не может быть представлен.Есть еще одна причина, по которой
String
класс Java должен быть окончательным: в некоторых сценариях он важен для безопасности. Если быString
класс не был финальным, следующий код был бы уязвим для скрытых атак злоумышленника:Атака: злонамеренный вызывающий объект может создать подкласс String,
EvilString
гдеEvilString.startsWith()
всегда возвращается true, но где значениеEvilString
является чем-то злым (например,javascript:alert('xss')
). Из-за подклассов это позволит избежать проверки безопасности. Эта атака известна как уязвимость, связанная с временем проверки (TOCTTOU): между временем, когда проверка выполнена (URL начинается с http / https), и временем, когда используется значение (для создания фрагмента HTML), эффективное значение может измениться. Если быString
не было окончательного, то риски TOCTTOU были бы повсеместными.Поэтому, если
String
это не окончательно, становится сложно написать безопасный код, если вы не можете доверять своему абоненту. Конечно, это именно то положение, в котором находятся библиотеки Java: они могут вызываться ненадежными апплетами, поэтому они не осмеливаются доверять своему вызывающему. Это означает, что было бы неразумно сложно писать безопасный код библиотеки, если бы онString
не был окончательным.источник
char[]
внутренней части строки, но это требует некоторой удачи во времени.char[]
внутри строки; следовательно, они не могут использовать уязвимость TOCTTOU.String
не были окончательными. Пока в какой-то момент эта модель угрозы актуальна, ее становится важно защищать. Пока это важно для апплетов и другого ненадежного кода, этого вполне достаточно, чтобы оправдатьString
окончательный вывод, даже если он не нужен для доверенных приложений. (В любом случае доверенные приложения могут уже обойти все меры безопасности, независимо от того, является ли он окончательным.)Если нет, то многопоточные Java-приложения были бы беспорядком (даже хуже, чем на самом деле).
ИМХО Главным преимуществом конечных строк (неизменяемых) является то, что они по своей природе поточно-ориентированы: они не требуют синхронизации (написание этого кода иногда довольно тривиально, но более чем менее чем далеко от этого). Если бы они были изменяемыми, гарантировать такие вещи, как многопоточные инструменты Windows, было бы очень сложно, и эти вещи строго необходимы.
источник
final
классы не имеют ничего общего с изменчивостью класса.Еще одним преимуществом
String
финальной версии является то, что она обеспечивает предсказуемые результаты, как и неизменность.String
хотя класс и предназначен для обработки в нескольких сценариях, например, тип значения (компилятор даже поддерживает его черезString blah = "test"
). Поэтому, если вы создаете строку с определенным значением, вы ожидаете, что она будет иметь определенное поведение, которое наследуется от любой строки, аналогично ожиданиям, которые вы имели бы с целыми числами.Подумайте об этом: что делать, если вы подкласс
java.lang.String
и переусердствовали методыequals
илиhashCode
? Внезапно две строки «test» уже не могли сравниться друг с другом.Вообразите хаос, если бы я мог сделать это:
какой-то другой класс:
источник
Фон
Есть три места, где final может отображаться на Java:
Завершение класса предотвращает все подклассы класса. Создание метода final не позволяет подклассам метода переопределять его. Создание поля final предотвращает его изменение позже.
заблуждения
Существуют оптимизации, которые происходят вокруг окончательных методов и полей.
Последний метод облегчает оптимизацию HotSpot с помощью встраивания. Тем не менее, HotSpot делает это, даже если метод не является окончательным, поскольку он работает при условии, что он не был переопределен, пока не доказано обратное.Подробнее об этом на SO
Последняя переменная может быть агрессивно оптимизирована, и больше об этом можно прочитать в разделе 17.5.3 JLS .
Однако с этим пониманием следует помнить, что ни одна из этих оптимизаций не предназначена для того, чтобы сделать финал класса . Нет никакого выигрыша в производительности, сделав финал класса.
Последний аспект класса также не имеет ничего общего с неизменяемостью. Можно иметь неизменный класс (такой как BigInteger ), который не является окончательным, или класс, который является изменяемым и окончательным (например, StringBuilder ). Решение о том, должен ли класс быть окончательным, является вопросом дизайна.
Окончательный дизайн
Строки являются одним из наиболее часто используемых типов данных. Их можно найти как ключи к картам, они хранят имена пользователей и пароли, это то, что вы читаете с клавиатуры или поля на веб-странице. Струны везде .
Карты
Первое, что нужно рассмотреть, если бы вы могли создать подкласс String, это осознать, что кто-то может создать изменяемый класс String, который в противном случае может выглядеть как String. Это повредило бы Карты повсюду.
Рассмотрим этот гипотетический код:
Это проблема с использованием изменяемого ключа в общем случае с картой, но я пытаюсь понять, что внезапно некоторые вещи в разрыве карты. Запись больше не находится в нужном месте на карте. В 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», которое обеспечивает, чтобы каждый класс был:
Рациональным для чего является:
Разрешение расширенных классов реализации означает, что подклассы могут повредить состояние класса, на котором он основан, и сделать так, чтобы различные гарантии, которые дает суперкласс, были недействительными. Для чего-то такого сложного, как String, почти наверняка, что изменение его части что- то сломает.
Разработчик хурбис
Это часть того, чтобы быть разработчиком. Рассмотрим вероятность того, что каждый разработчик создаст свой собственный подкласс String со своей собственной коллекцией утилит . Но теперь эти подклассы нельзя свободно присваивать друг другу.
Этот путь ведет к безумию. Приведение к String повсеместно и проверка, является ли String экземпляром вашего класса String или нет, создание новой String на основе этого и ... просто нет. Не.
Я уверен , что вы можете написать хороший класс String , ... но оставить написание несколько реализаций строк для тех сумасшедших людей , которые пишут на C ++ и приходится иметь дело с
std::string
иchar*
и что - то от повышения и SString , и все остальное .Java String magic
Есть несколько волшебных вещей, которые Java делает со строками. Это облегчает работу программиста, но вносит некоторые несоответствия в язык. Разрешение подклассов на String заставит задуматься о том, как справиться с этими волшебными вещами:
Строковые литералы (JLS 3.10.5 )
Имея код, который позволяет сделать:
Это не следует путать с боксом числовых типов, таких как Integer. Вы не можете сделать
1.toString()
, но вы можете сделать"foo".concat(bar)
.+
Оператор (ПСБ 15.18.1 )Никакой другой ссылочный тип в Java не позволяет использовать оператор. Строка особенная. Оператор конкатенации строк также работает на уровне компилятора, так что он
"foo" + "bar"
становится,"foobar"
когда он компилируется, а не во время выполнения.Преобразование строк (JLS 5.1.11 )
Все объекты могут быть преобразованы в строки, просто используя их в контексте строки.
Строковый интернинг ( JavaDoc )
Класс String имеет доступ к пулу Strings, что позволяет ему иметь канонические представления объекта, который заполняется в типе компиляции литералами String.
Разрешение подкласса String будет означать, что эти биты со String, которые облегчают программирование, станут очень трудными или невозможными, когда возможны другие типы String.
источник
Если бы String не был окончательным, каждый сундук для программиста содержал бы свою собственную «String с несколькими хорошими вспомогательными методами». Каждый из них будет несовместим со всеми другими сундуками с инструментами.
Я посчитал это глупым ограничением. Сегодня я убежден, что это было очень разумное решение. Он хранит строки как строки.
источник
final
в Java это не то же самое, чтоfinal
в C #. В этом контексте это то же самое, что и в C #readonly
. Смотрите stackoverflow.com/questions/1327544/…public final class String
.