JavaRush /Java блог /Архив info.javarush /Перевод: Создание объектов типа String в Java — использов...
FellowSparrow
12 уровень
Lvov

Перевод: Создание объектов типа String в Java — использование " " или конструктора?

Статья из группы Архив info.javarush
Оригинал: Create Java String Using ” ” or Constructor? By X Wang Перевод: Создание объектов типа String в Java — использование В Java строка может быть создана с использованием двух способов:

String x = "abc";
String y = new String("abc");
В чём же разница между использованием двойных кавычек и использованием конструктора?

1. Двойные Кавычки vs. Конструктор

На этот вопрос можно ответить, рассмотрев два простых примера. Пример 1:

String a = "abcd";
String b = "abcd";
System.out.println(a == b);  // True
System.out.println(a.equals(b)); // True
a==b истинно потому, что a и b ссылаются на один и тот же объект — строку, объявленную как литерал (строковый литерал далее) в области методов (отсылаем читателя к источнику на нашем ресурсе: Топ 8 диаграмм для понимания Java, диаграмма 8). Когда один и тот же строковый литерал создан более одного раза, в памяти сохраняется только одна копия строки, только лишь один ее экземпляр (в нашем случае "abcd"). Это называется "интернирование строк". Все строковые константы, обрабатываемые на этапе компиляции, в Java интернируются автоматически. Пример 2:

String c = new String("abcd");
String d = new String("abcd");
System.out.println(c == d);  // False
System.out.println(c.equals(d)); // True
c==d ложно потому, что c и d ссылаются на два различных объекта в памяти (в куче). Различные объекты всегда имеют разные ссылки. Эта диаграмма иллюстрирует две вышеописанные ситуации: Перевод: Создание объектов типа String в Java — использование

2. Интернирование строк на этапе выполнения программы

Автор благодарит LukasEder (комментарий ниже принадлежит ему): Интернирование строк может происходить также и во время выполнения программы, даже если две строки созданы с помощью конструкторов:

String c = new String("abcd").intern();
String d = new String("abcd").intern();
System.out.println(c == d);  // Now true
System.out.println(c.equals(d)); // True

3. Когда же использовать двойные кавычки и когда — конструкторы

Вследствие того, что литерал "abcd" всегда имеет тип String, использование конструктора создаст дополнительно ненужный объект. Таким образом, двойные кавычки должны быть использованы, если вам необходимо просто создать строку. Если вам действительно необходимо создать новый объект в куче, вы должны использовать конструктор. Здесь (оригинал) показаны варианты использования. (Переведенный текст привожу далее. Но все же весьма рекомендую ознакомиться с кодом комментаторов по этой ссылке.)

Метод substring() в JDK 6 и JDK 7

The substring() Method in JDK 6 and JDK 7 By X Wang Метод substring(int beginIndex, int endIndex) в JDK 6 и JDK 7 различаются. Знание этих отличий может помочь вам лучше использовать этот метод. Ради удобства прочтения далее под substring() будем подразумевать полный синтаксис, т.е. substring(int beginIndex, int endIndex).

1. Что делает substring()?

Метод substring(int beginIndex, int endIndex) возвращает строку, которая начинается с символа под номером beginIndex и заканчивается символом под номером endIndex-1.

String x = "abcdef";
x = x.substring(1,3);
System.out.println(x);
Output:

bc

2. Что происходит при вызове substring()?

Вы можете знать, что вследствие неизменяемости x, при присвоении x результата x.substring(1,3), x указывает на полностью новую строку (см. диаграмму): Перевод: Создание объектов типа String в Java — использование Однако же, эта диаграмма не полностью правильна; она не демонстрирует, что на самом деле происходит в куче. То, что в самом деле происходит, когда вызывается substring(), отличается в JDK 6 и JDK 7.

3. substring() в JDK 6

Строковый тип поддерживается массивом типа char. В JDK 6 класс String содержит 3 поля: char value[], int offset, int count. Они используются для хранения реального массива символов, индекса первого символа в массиве, числа символов в строке. Когда вызван метод substring(), он создает новую строку, но значение переменной все еще указывает на тот же самый массив в куче. Разница между двумя строками заключается в их числе символов и значении индекса начального символа в массиве. Перевод: Создание объектов типа String в Java — использование Нижеприведенный код упрощен и только содержит только основное для демонстрации проблемы.

//JDK 6
String(int offset, int count, char value[]) {
	this.value = value;
	this.offset = offset;
	this.count = count;
}
 
public String substring(int beginIndex, int endIndex) {
	//check boundary
	return  new String(offset + beginIndex, endIndex - beginIndex, value);
}

4. Проблема, вызванная substring() в JDK 6

Если у вас есть ОЧЕНЬ длинная строка, но вам нужна только маленькая ее часть, которую вы получаете каждый раз используя substring(). Это вызовет проблемы при исполнении, с того момента как вам нужна маленькая часть, вы все равно вынуждены хранить всю строку целиком. Для JDK 6 решение состоит в приведенном коде, который приведет строку к настоящей подстроке:

x = x.substring(x, y) + ""
Пользователь STepeR сформулировал вопрос (см. его комментарий), и показалось нужным дополнить п.4. "Проблема, вызванная substring() в JDK 6" более обширным примером. Надеюсь, это и будет ответом и поможет другим быстрее разобраться, в чем же суть проблемы. Вот код:

String a = "aLongLongString";
String b = a.substring(1, 2);
String c = a.substring(2, 6);
Итак, в JDK 7 объекты b,с типа String, созданные в результате вызова метода substring() объекта a типа String, будут ссылаться на два вновь созданных массива в хипе — L для b, ongL для c. Эти два новых массива будут хранитья в хипе НАРЯДУ с исходным массивом aLongLongString, на который ссылается a. Т.е. исходный массив никуда не исчезает. Теперь возвратимся к JDK 6. В JDK 6 в хипе находится один-единственный массив aLongLongString. После выполнения строчек кода

String b = a.substring(1, 2);
String c = a.substring(2, 6);
объекты b, c ссылаются все на тот же массив в хипе, соответствующий объекту a: b — на элементы с 1-го индекса до 2-го, c — на элементы со 2-го индекса до 6-го (нумерация начинается с 0-го,напоминание). Т.е. очевидно, что всякое дальнейшее обращение к переменным b или c в JDK 6 на самом деле приведет к "отсчитыванию" нужных элементов исходного массива в хипе. В JDK 7 всякое дальнейшее обращение к переменным b или c вызовет обращение к нужным массивам меньшего размера, которые уже созданы и "живут" в хипе. Т.е. явно JDK 7 в подобных ситуациях использует физически больше памяти. Но давайте представим возможный вариант: переменным b и c присвоены некие подстроки переменной a, и в дальнейшем все используют только их — объекты b и c. К переменной a просто уже никто не обращается, на нее нету ссылок (именно это имеет ввиду автор статьи). В итоге в какой-то момент времени срабатывает сборщик мусора, и (в самом общем виде, конечно) получаем 2 различных ситуации: JDK 6: уничтожен (garbage collected) объект a, НО - исходный громадный массив в хипе жив; его постоянно используют b и c. JDK 7: уничтожен объект a вместе с исходным массивом в хипе. Вот этот момент в JDK 6 может привести к утечке памяти (memory leak).

5. substring() в JDK 7

В JDK 7 метод улучшен. В JDK 7 substring() в самом деле создает новый массив в куче. Перевод: Создание объектов типа String в Java — использование

//JDK 7
public String(char value[], int offset, int count) {
	//check boundary
	this.value = Arrays.copyOfRange(value, offset, offset + count);
}
 
public String substring(int beginIndex, int endIndex) {
	//check boundary
	int subLen = endIndex - beginIndex;
	return new String(value, beginIndex, subLen);
}
Комментарии (10)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
jaroslav Уровень 25
17 октября 2018
Класно) Дякую, тепер я більше розумію.
provisota Уровень 33
7 марта 2015
Тут опечатка:
Итак, в JDK 7 объекты b, с типа String, созданные в результате вызова метода substring()
объекта a типа String, будут ссылаться на два вновь созданных массива в хипе — «L» для b,
«ongL» для a.
должно быть
Итак, в JDK 7 объекты b, с типа String, созданные в результате вызова метода substring()
объекта a типа String, будут ссылаться на два вновь созданных массива в хипе — «L» для b,
«ongL» для с.
provisota Уровень 33
7 марта 2015
Интересный нюанс с методом intern()
String c = new String("abcd").intern();
String d = new String("abcd").intern();
System.out.println(c == d);  // Now true
System.out.println(c.equals(d)); // True
Спасибо…
STepeR Уровень 18
26 февраля 2015
Я правильно понимаю, что в случае substring() в JDK 6, когда мне нужна не только новая строка, но и продолжение работы со старой, например:
String a = «aLongLongString»;
String b = a.subString(1, 2);
String c = a.subString(2, 6);
и т.д.
, то в этом случае от особенности JDK 6 я только выиграю? И можно ли как-то это поведение воспроизвести в JDK 7?
Gradus Уровень 27
26 февраля 2015
полезно знать, спасибо!
Photosmart13 Уровень 20
16 февраля 2015
Статья очень познавательна и легка для понимания. ++ Спасибо.
maks1m Уровень 25
16 февраля 2015
Спасибо, познавательно.