Вы находитесь на странице: 1из 4

Перевод: Создание объектов типа String в Java —

использование " " или конструктора?


Статья из группы Архив info.javarush.ru
участников
Вы в группе

Оригинал: Create Java String Using ” ” or Constructor? By X Wang В 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 ссылаются на два различных объекта в памяти (в
куче). Различные объекты всегда имеют разные ссылки. Эта диаграмма

иллюстрирует две вышеописанные ситуации:

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 указывает на полностью новую строку (см.

диаграмму): Однако же, эта диаграмма не полностью правильна; она не


демонстрирует, что на самом деле происходит в куче. То, что в самом деле
происходит, когда вызывается substring(), отличается в JDK 6 и JDK 7.

3. substring() в JDK 6
Строковый тип поддерживается массивом типа char. В JDK 6 класс String
содержит 3 поля: char value[], int offset, int count. Они используются
для хранения реального массива символов, индекса первого символа в массиве,
числа символов в строке. Когда вызван метод substring(), он создает новую
строку, но значение переменной все еще указывает на тот же самый массив в
куче. Разница между двумя строками заключается в их числе символов и
значении индекса начального символа в массиве. Нижеприведенный код
упрощен и только содержит только основное для демонстрации проблемы.
//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() в самом деле создает новый

массив в куче.
//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);
}