Pull to refresh

Comments 33

Укажите, пожалуйста, где Вы исказили исходный текст, раз уж признались.

Получается, Java содержит ещё и скрытую хэш-таблицу для интернирования строк?
Она(хэш-таблица) называется пулом стрингов.
Немного вас поправлю. Метод intern просто перед созданием объекта String смотрит есть ли этот объект в пуле стрингов и возвращает его. Иначе создается новый объект в пуле.
Суть целого поста поместилась в одном предложении. Кр. с. т.
По поводу краткости я, конечно, согласен. Однако мне кажется, что одного предложения все же недостаточно, чтобы рассказать о смысле и особенностях интернирования строк, а также случаях, когда его использование оправданно.
Только наверно метод intern вызывается уже после создания объекта String и в случае чего возвращает объект из пула.
When the intern method is invoked, if the pool already contains a string equal to this String object as determined by the equals(Object) method, then the string from the pool is returned. Otherwise, this String object is added to the pool and a reference to this String object is returned.

JavaDoc
А разве я не это же сказал?
Сначало делается new String() потом у этого стринга вызывается intern который может вернуть указатель на другой объект, но главное что вызывается это уже после создания того первого экземпляра строки, который правда соберет сборщик мусора.
Раз уж признался, укажу: добавил пояснение в основной алгоритм для большей ясности, в каком случае говорится об одинаковых строках, а в каком об идентичных объектах, а также еще где-то, где это уже не так существенно.
Так же хочу уточнить, что 'основной алгоритм' — это обобщенная идея интернирования, а сама реализация String.intern() нативна и о ней мне сложно что-либо сказать со 100% уверенностью.
Правильно ли я понимаю, что s.intern()==const по сравнению с s.equals(const) не имеет смысла, а вот если сравнений с константами несколько — то это уже быстрее?
К сожалению, наездом на Xerces автор статьи показал не крутое знание intern, а плохое знание того, как работают константы в Java.

Вопрос о том, зачем в Xerces так написали, задавался неоднократно. И на него есть простой ответ:

It prevents the field being a compile time constant.

References to final String fields which are compile time constants are compiled as a literal, not as an access to the field.

In the example, if there was no .intern(), anything referencing the library would have to be recompiled when the value of the schema namespace is updated. So the authors added 'intern()', making the value not a compile time constant, so the client code desn't have to be rebuilt whhen the library is updated.


Недостаток данной строки в Xerces состоит в том, что не написан комментарий, объясняющий смысл этой операции. А сам код — правильный.

В течении прошлого года я 4 раза убеждался, что даже Senior Java Developer с опытом работы 5-6 лет может не подозревать о механизме инлайнинга констант и о том, какой геморрой это создает в продакшене при выпуске обновлений.
Вот оно что… Но можно по-подробнее о проблемах в продакшене вызванных инлайнингом констант?
UFO just landed and posted this here
может быть правильней хотя бы для сборки продакшн пользоваться мавеном?
Безусловно для сборки — да. Но IRL есть еще такое понятие, как hotpatch :)
Если в классе объявлена final-константа, компилятор может в других классах, ссылающихся на нее поставить не ссылку, а непосредственно ее значение. Поэтому, казалось бы изменили константу в одном классе, перекомпилировали только этот класс и залили на продакшн. И как-будто бы приложение не видит изменений.
Проблема проявляется, например, для IDE при вычислении зависимостей между классами. Если бы константы не инлайнились, то по пулу строк, который живет в начале каждого класса, можно было бы вычислить множество всех классов, на которые ссылается данный.

IDE должна знать зависимости, например, для того, чтобы при изменении одного класса корректно определять множество классов, которое следует перекомпилировать. Из-за инлайна констант в байт-коде теряется информация compile-time зависимости.
Хм… отличное пояснение. Хотя я не уверен, что константа «version» может со временем измениться в отличии от URI, рассмотренного на форуме… Или это пропаганда того, чтобы везде избегать инлайнинга констант?
Если упрощенно: следует избегать инлайнинга констант с видимостью public и protected.
UFO just landed and posted this here
Вообще-то это можно сказать про любые public или protected final переменные. Но такое как раз сейчас используется не часто, благодаря тому же IOC, а в примере действительно такое, что изменяться вряд ли будет.
Если упрощенно — да, про любые. Частота использования зависит от coding style.

Что же до «вряд ли будет изменяться» — это из серии «фразы, которые не должен говорить вслух вменяемый архитектор».
А если не упрощенно, то не про любые, так что ли?

Вменяемый архитектор (и невменяемый тоже) отвечает за продукт и делает то, что считает нужным в конкретной ситуации.
Если не упрощенно, то ответ такой: как и везде при проектировании все определяется тем, как планируется эту константу использовать.

Если НУЖНО, чтобы она была заинлайнена — тогда делаем литералом. И пишем комментарий с причиной. Это может иметь смысл, когда мы намеренно требуем обеспечить синхронную пересборку нескольких отдельных единиц деплоймента. Либо если мы ТОЧНО знаем, что эта константа НИКОГДА не может измениться. К примеру, нет причин запрещать инлайнинг константы MONTHS_PER_YEAR.

Во всех остальных случаях безопаснее блокировать инлайнинг.

Т.е. мы имеем два случая:

1. private и package private — пусть инлайнятся.
2. public и protected — разрешаем инлайнинг только если к тому есть явные показания.

(далее — отход от основной темы)

Что до ответственности за продукт — архитектор не должен мыслить категориями «вряд ли». Именно из соображений ответственности.

К примеру, если мы используем как уникальный идентификатор домен сайта компании, то обоснование «он вряд ли изменится» является признаком безответственности. Поскольку после ребрендинга или слияния домен может поменяться, а старый — освободиться и достаться другой конторе. Или его могут тупо продать за стопиццот тыщщ баксов.

Соответственно, вменяемый архитектор хотя бы опишет неизменность домена как assumption в проектных документах. А совсем умный (таких пока не встречал даже в зеркале) не забудет про раздел «управление рисками».

А вот всякие «вряд ли» есть грех.
Ну и как это противоречит тому, что я сказал? Намного больше слов?

И как «эта константа НИКОГДА не может измениться» соотносит с «Что же до «вряд ли будет изменяться» — это из серии «фразы, которые не должен говорить вслух вменяемый архитектор»»?

«Вряд ли» на математическом языке означает с вероятностью 0.99 (или 0.95, или 0.8 — в зависимости от сложности переделки) и бывает такое часто. А НИКОГДА — вероятность 1, что как раз редкость.

Все это словоблудие, рассчитанное на заказчика. Суть в том, что есть конкретная ситуация, которая решается конкретными методами. И если у нас сейчас миллирднооборотный клиент, который принимает Visa, Amex и Master Card, то, соответственно, никто не станет включать названия этих карт в конфигураторы и ни в какие «управления рисками» тоже, ибо все прекрасно понимают — чтобы включить в список, скажем, Diners Club, потребуется год переговоров, одной пицы съедят на миллион, так что добавление еще одного типа карты на этом фоне — полная фигня, поэтому нет смысла марать бумагу всякими глупостями.
>>Очевидно, что эта строка будет использоваться для многократных сравнений. Имеет ли смысл интернировать ее?

>>Конечно, имеет. Вот почему Java уже это делает.

Тут не все так однозначно. Не знаю, как насчет Java, но в CLR по умолчанию задается атрибут, ограничивающий «массовое интернирование».
UFO just landed and posted this here
Я в Java совсем новичок, но тут столкнулся со следующей задачей. Имеется коллекция, где объекты создаются по запросу примерно так:
Object get(String name) {
  Object res = cache.get(name);
  if(res == null) {
    res = createElement(name);
    if(res != null) cache.put(name);
  }
  return res;
}

Возникла такая проблема, что создание некоторых объектов (вызов createElement) долгое и ресурсоёмкое и при этом сразу несколько тредов могут запросить создание одного и того же объекта одновременно (то есть пока один тред обрабатывает createElement, другой снова заходит в этот get и тоже приступает к createElement, потому что объекта ещё нет в кэше). Добавил в объявление метода get волшебное слово synchronized, но стало ещё хуже, потому что если один тред создаёт долгий элемент, то другие ждут его, хотя они запросили совсем другой быстрый элемент. То есть синхронизация нужна только при запросе одного и того же элемента. Обернул тело функции в блок synchronized(name) {}, но это не сработало. Тут я вспомнил, что что-то такое недавно читал на Хабре :-) Изменил на synchronized(name.intern()) {}. После этого как будто бы стало всё прекрасно, так что автору спасибо за подсказку :-) Интересно, это правильное решение задачи или есть подводные камни, о которых я не подумал?
Если ключи длинные и их большое количество (разных), то возможно будет большой расход памяти.
Думаю что действительно далеко не всё предусмотрено, завит от того что за структура этот ваш cache. Если например, туда поставить HashMap, то уже неправильно, так как put операция не атомарная и вполне возможны ситуации, когда добавляются разные элементы и какой-то из них теряется.

Про память уже сказали.

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

Извините, но Шипилёву я верю больше, чем автору статьи. И он говорит, что использовать intern() как правило не стоит.
Sign up to leave a comment.

Articles