Яка різниця між "текстом" та новим рядком ("текстом")?


195

Яка різниця між цими двома наступними твердженнями?

String s = "text";

String s = new String("text");


Будь-хто будь-ласка, відповідь на це. Рядок a = "Java"; Рядок b = "Java"; System.out.println (a == b); true // але System.out.println ("a == b?" + a == b); // помилково ...
Енергія

я не розумію, коли я додав коментар ("a == b?) => мій результат стає НЕВЕРСЬКИМ. чому?
Energy

@Energy Результат falseпояснюється тим, що порядок операцій диктує, що + оператор виходить першим, об'єднуючи "a == b?" з a, щоб створити рядок "a == b? Java". Тоді вираз "a==b?Java" == bоцінюється на хибне.
Елісон Б

@AllisonB отримав це, велике спасибі!
Енергія

Відповіді:


187

new String("text"); явно створює новий і референційно виразний екземпляр Stringоб'єкта; String s = "text";може повторно використовувати екземпляр з пулового постійного пулу, якщо такий є в наявності.

Ви дуже рідко хотіли б використовувати new String(anotherString)конструктор. З API:

String(String original): Ініціалізує новостворений String об’єкт, щоб він представляв ту саму послідовність символів, що і аргумент; іншими словами, новостворена рядок - це копія рядка аргументу. Якщо не потрібна явна копія оригіналу, використання цього конструктора зайве, оскільки рядки незмінні.

Пов'язані питання


Що означає референтне розрізнення

Вивчіть такий фрагмент:

    String s1 = "foobar";
    String s2 = "foobar";

    System.out.println(s1 == s2);      // true

    s2 = new String("foobar");
    System.out.println(s1 == s2);      // false
    System.out.println(s1.equals(s2)); // true

==для двох типів еталонних даних - порівняння еталонної ідентичності. Два об'єкти, які equalsє необов’язково ==. Зазвичай це неправильно використовувати ==для еталонних типів; більшість часу equalsпотрібно використовувати замість цього.

Тим не менш, якщо з будь-якої причини вам потрібно створити дві, equalsале не ==рядкові, ви можете використовувати new String(anotherString)конструктор. Однак потрібно ще раз сказати, що це дуже своєрідно і це рідко є наміром.

Список літератури

Пов'язані питання


3
Якщо я напишу: String s = new String ("abc"); А зараз я пишу: String s = "abc"; Буде String s = "abc"; створити нову лінійку String в пулі String?
Kaveesh Kanwal

Чому ніхто не відповідає на попереднє запитання?
zeds

2
@KaveeshKanwal Ні, літерал не буде дублюватися. Як бачите, є 2 "abc"с. Тільки один з них піде до пулу String, а інший буде посилатися на нього. Тоді є те, sщо буде належним новим об’єктом.
Каяман

1
@Kaveesh Kanwal - String s = new String ("abc") створить лише новий об'єкт String зі значенням "abc". І 2-е твердження перевірить, чи є який-небудь "abc" рядковий літерал вже присутній у String Pool чи ні. Якщо він вже присутній, то повертається посилання на існуючий, а якщо ні, то в пулі String створюється новий літерал ("abc"). Сподіваюся, він вирішить ваш запит !!
user968813

У цьому немає "може". Компілятор повинен об'єднати літеральні рядки. JLS 3.10.5 .
Маркіз Лорн

119

Строкові літерали перейдуть у String Constant Pool .

Наведений нижче знімок може допомогти вам зрозуміти це візуально, щоб запам'ятати його довше.

введіть тут опис зображення


Рядок за рядком створення об’єкта:

String str1 = new String("java5");

Використовуючи рядковий буквальний "java5" у конструкторі, нове значення рядка зберігається у постійному пулі рядків. Використовуючи новий оператор, новий рядковий об’єкт створюється в купі з значенням "java5".

String str2 = "java5"

Посилання "str2" вказується на вже збережене значення в рядку постійного пулу

String str3 = new String(str2);

Новий об'єкт рядка створюється в купі з тим же значенням, що і посилання на "str2"

String str4 = "java5";

Посилання "str4" вказується на вже збережене значення в рядку постійного пулу

Всього об'єктів: Купа - 2, басейн - 1

Подальше читання про спільноту Oracle


1
Хороша відповідь .. але ми хотіли б знати, що зараз я giong змінити значення str1 = "java6", то це змінить значення str4?
CoronaPintu

2
так, я перевірив, що це не змінить значення str4
CoronaPintu,

@Braj Чи можете ви надати документацію твердження вашого відповіді?
Василь Бурк

@Braj: Чи повинні заголовки "Купи" та "пулу" в таблиці бути зворотними?
Рахул Куруп

Неправильно. Постійний пул створюється під час компіляції, а не під час виконання. Не використовуйте форматування цитат для тексту, який не цитується.
Маркіз Лорн

15

Один створює String у струнному басейні String

String s = "text";

інший створює рядок у постійному пулі ( "text"), а інший рядок у звичайному просторі купи ( s). Обидва рядки матимуть однакове значення, що і "текст".

String s = new String("text");

s потім втрачається (придатний для GC), якщо пізніше не використовується.

З іншого боку, рядкові літерали використовуються повторно. Якщо ви використовуєте "text"в декількох місцях вашого класу, це насправді буде одна і лише одна струна (тобто кілька посилань на одну і ту ж рядок в пулі).


Струни в постійному басейні ніколи не втрачаються. Ви мали на увазі сказати, що "s" втрачено, якщо пізніше не буде використано?
Маркіз Лорн

@EJP: так, я мав на увазі "s". Дякуємо, що помітили. Я виправлю питання.

9

JLS

Концепція JLS називає "інтернування".

Відповідний уривок з JLS 7 3.10.5 :

Більше того, рядковий літерал завжди посилається на один і той же екземпляр класу String. Це тому, що рядкові літерали - або, загалом, рядки, які є значеннями постійних виразів (§15.28) - "інтерновані", щоб поділитися унікальними екземплярами, використовуючи метод String.intern.

Приклад 3.10.5-1. Строкові літерали

Програма, що складається з блоку компіляції (§7.3):

package testPackage;
class Test {
    public static void main(String[] args) {
        String hello = "Hello", lo = "lo";
        System.out.print((hello == "Hello") + " ");
        System.out.print((Other.hello == hello) + " ");
        System.out.print((other.Other.hello == hello) + " ");
        System.out.print((hello == ("Hel"+"lo")) + " ");
        System.out.print((hello == ("Hel"+lo)) + " ");
        System.out.println(hello == ("Hel"+lo).intern());
    }
}
class Other { static String hello = "Hello"; }

і компіляційний блок:

package other;
public class Other { public static String hello = "Hello"; }

виробляє вихід:

true true true true false true

JVMS

JVMS 7 5.1 говорить :

Строковий літерал - це посилання на екземпляр класу String і походить від CONSTANT_String_info структури (§4.4.3) у двійковому поданні класу чи інтерфейсу. Структура CONSTANT_String_info дає послідовність точок коду Unicode, що складають рядковий літерал.

Мова програмування Java вимагає, щоб однакові рядкові літерали (тобто літерали, які містять однакову послідовність точок коду), повинні посилатися на той самий екземпляр класу String (JLS §3.10.5). Крім того, якщо метод String.intern викликається в будь-якому рядку, результат є посиланням на той самий екземпляр класу, який буде повернуто, якби ця рядок виявилася як буквальна. Таким чином, наступний вираз повинен мати значення true:

("a" + "b" + "c").intern() == "abc"

Щоб отримати рядковий літерал, віртуальна машина Java вивчає послідовність кодів, заданих структурою CONSTANT_String_info.

  • Якщо раніше метод String.intern був викликаний на екземпляр класу String, що містить послідовність точок коду Unicode, ідентичну тій, що задана структурою CONSTANT_String_info, то результат рядкового виведення рядка є посиланням на той самий екземпляр класу String.

  • В іншому випадку створюється новий екземпляр класу String, що містить послідовність точок коду Unicode, заданих структурою CONSTANT_String_info; посилання на цей екземпляр класу є результатом рядкового буквеного виведення. Нарешті, використовується інтерн-метод нового екземпляра String.

Байт-код

Також корисно подивитися на реалізацію байт-коду на OpenJDK 7.

Якщо ми декомпілюємо:

public class StringPool {
    public static void main(String[] args) {
        String a = "abc";
        String b = "abc";
        String c = new String("abc");
        System.out.println(a);
        System.out.println(b);
        System.out.println(a == c);
    }
}

у нас на постійному басейні:

#2 = String             #32   // abc
[...]
#32 = Utf8               abc

і main:

 0: ldc           #2          // String abc
 2: astore_1
 3: ldc           #2          // String abc
 5: astore_2
 6: new           #3          // class java/lang/String
 9: dup
10: ldc           #2          // String abc
12: invokespecial #4          // Method java/lang/String."<init>":(Ljava/lang/String;)V
15: astore_3
16: getstatic     #5          // Field java/lang/System.out:Ljava/io/PrintStream;
19: aload_1
20: invokevirtual #6          // Method java/io/PrintStream.println:(Ljava/lang/String;)V
23: getstatic     #5          // Field java/lang/System.out:Ljava/io/PrintStream;
26: aload_2
27: invokevirtual #6          // Method java/io/PrintStream.println:(Ljava/lang/String;)V
30: getstatic     #5          // Field java/lang/System.out:Ljava/io/PrintStream;
33: aload_1
34: aload_3
35: if_acmpne     42
38: iconst_1
39: goto          43
42: iconst_0
43: invokevirtual #7          // Method java/io/PrintStream.println:(Z)V

Зверніть увагу, як:

  • 0і 3: однакова ldc #2константа завантажена (буквали)
  • 12: створюється новий екземпляр рядка (з #2аргументом)
  • 35: aі cпорівнюються із звичайними об'єктамиif_acmpne

Представлення постійних рядків є досить магічним у байт-коді:

  • вона має виділену CONSTANT_String_info структуру, на відміну від звичайних об'єктів (наприклад new String)
  • структура вказує на структуру CONSTANT_Utf8_info, яка містить дані. Це єдині необхідні дані для представлення рядка.

а цитата JVMS вище, схоже, говорить про те, що всякий раз, коли вказано на Utf8, є однаковим, тоді завантажуються однакові екземпляри ldc.

Я зробив подібні тести для полів, і:

  • static final String s = "abc"вказує на постійну таблицю через атрибут ConstantValue
  • не завершальні поля не мають цього атрибуту, але все ще можуть бути ініціалізовані ldc

Висновок : існує пряма підтримка байт-коду для пулу рядків, і представлення пам'яті є ефективним.

Бонус: порівняйте це з пулом Integer , який не підтримує прямий байт-код (тобто немає CONSTANT_String_infoаналога).


2

@Braj: Я думаю, ти згадав про зворотне. Будь ласка, виправте мене, якщо я помиляюся

Рядок за рядком створення об’єкта:

String str1 = new String ("java5")

   Pool- "java5" (1 Object)

   Heap - str1 => "java5" (1 Object)

String str2 = "java5"

  pool- str2 => "java5" (1 Object)

  heap - str1 => "java5" (1 Object)

String str3 = новий рядок (str2)

  pool- str2 => "java5" (1 Object)

  heap- str1 => "java5", str3 => "java5" (2 Objects)

String str4 = "java5"

  pool - str2 => str4 => "java5" (1 Object)

  heap - str1 => "java5", str3 => "java5" (2 Objects)

str1не бере участі в вартості str2або str3або str4яким - небудь чином.
Маркіз Лорн

1

Подумайте "bla", як магічна фабрика на кшталт Strings.createString("bla")(псевдо). Фабрика містить пул усіх струн, створених таким чином.

Якщо його викликають, він перевіряє, чи є в пулі вже рядок із цим значенням. Якщо true, він повертає цей рядковий об'єкт, отже, рядки, отримані таким чином, справді є тим самим об'єктом.

Якщо ні, то він створює новий рядковий об'єкт внутрішньо, зберігає його в пулі і потім повертає. Таким чином, коли наступне запит на те саме значення рядка, він повертає той самий екземпляр.

Створення вручну new String("")переосмислює цю поведінку, минаючи пул літеральних рядків. Отже, рівність завжди повинна перевірятися, використовуючи, equals()яка порівнює послідовність символів замість об'єктної опорної рівності.


"Чарівна фабрика", на яку ви посилаєтеся, є нічим іншим, ніж компілятором Java. Помилково писати цей процес так, ніби він відбувся під час виконання.
Маркіз Лорн

1

Один простий спосіб зрозуміти різницю нижче:

String s ="abc";
String s1= "abc";
String s2=new String("abc");

        if(s==s1){
            System.out.println("s==s1 is true");
        }else{
            System.out.println("s==s1 is false");
        }
        if(s==s2){
            System.out.println("s==s2 is true");
        }else{
            System.out.println("s==s2 is false");
        }

вихід є

s==s1 is true
s==s2 is false

Таким чином, новий String () завжди створюватиме новий екземпляр.


1

Будь-який рядковий літерал створюється всередині пулу літеральних рядків, і пул не дозволяє дублювати. Таким чином, якщо два або більше рядкових об'єктів ініціалізуються з однаковим буквальним значенням, то всі об'єкти будуть вказувати на один і той же буквальний.

String obj1 = "abc";
String obj2 = "abc";

"obj1" і "obj2" будуть вказувати на один і той же літеральний рядок, і пул літеральних рядків матиме лише один буквальний "abc".

Коли ми створюємо об'єкт класу String за допомогою нового ключового слова, створений таким чином рядок зберігається в купі пам'яті. Будь-який літеральний рядок, переданий як параметр конструктору класу String, проте зберігається у пулі рядків. Якщо ми створимо кілька об’єктів, використовуючи одне і те ж значення з новим оператором, кожен об’єкт буде створюватися в купі кожного разу, через це слід уникати нового оператора.

String obj1 = new String("abc");
String obj2 = new String("abc");

"obj1" і "obj2" будуть вказувати на два різних об'єкти в купі, а пул літеральних рядків матиме лише один буквальний "abc".

Крім того, що варто відзначити щодо поведінки рядків, це те, що будь-яке нове призначення або конкатенація, виконана на рядку, створює новий об'єкт у пам'яті.

String str1 = "abc";
String str2 = "abc" + "def";
str1 = "xyz";
str2 = str1 + "ghi";

Тепер у вищенаведеному випадку:
Рядок 1: "abc" літерал зберігається в рядку пулу.
Рядок 2: Буквал "abcdef" зберігається в пулі рядків.
Рядок 3: Новий літерал "xyz" зберігається в пулі рядків, і "str1" починає вказувати на цей літерал.
Рядок 4: Оскільки значення генерується додаванням до іншої змінної, результат зберігається в купі пам’яті, а літеральний доданий «ghi» перевірятиметься на його існування у пулі рядків та буде створено, оскільки його не існує в вищевикладений випадок.


0

Хоча це виглядає так само з точки зору програмістів, це має великий вплив на продуктивність. Ви б хотіли використовувати першу форму майже завжди.


0
String str = new String("hello")

Він перевірить, чи постійно в базі String постійно міститься String "привіт"? Якщо він присутній, він не додасть запис у постійний пул String. Якщо немає, то він додасть запис у постійний пул String.

Об'єкт буде створений у зоні пам’яті купи та strопорних точок для об’єкта, створеного в місці пам'яті купи.

якщо ви хочете strпосилатися на об'єкт точки, що міститься в String постійному пулі, тоді потрібно явно викликатиstr.intern();

String str = "world";

Він перевірить, чи постійно в базі String постійно міститься String "привіт"? Якщо він присутній, він не додасть запис у постійний пул String. Якщо немає, то він додасть запис у постійний пул String.

В обох вищевказаних випадках в басейні "Константа" розміщено strорієнтир на Струну "world".


"Це" - компілятор Java. Літеральний рядок створює унікальний запис у постійному пулі, під час компіляції. Помилково охарактеризувати цей процес так, ніби він трапляється під час виконання ..
Маркіз Лорн

Чи можете ви, зрозуміло, пояснили, що в цій публікації не так?
Jayesh

Що не так у цій публікації, це те, що літеральний рядок об'єднується під час компіляції, як я вже говорив. Не при виконанні коду, як у вашій відповіді.
Маркіз Лорн

@EJP Я ціную вашу відповідь. Чи можете ви, будь ласка, вказати точний рядок, який не відповідає у відповіді. Я бачу, що всі відповіді вище такі ж, як і те, що я написав. Будь ласка, допоможіть, я хочу виправити своє розуміння. Дякую.
Jayesh

Ви писали про весь процес так, ніби все відбувається, коли виконується рядок коду, що, як я вже не раз вам казав, не так. Ви не можете звести все це до того, що одна «точна лінія» не відповідає в своїй відповіді.
Маркіз Лорн

0

Коли ви зберігаєте String як

String string1 = "Hello";

безпосередньо, тоді JVM створює об'єкт String із заданою ціною під час окремого блоку пам'яті під назвою String постійний пул.

І кожного разу, коли ми маємо тенденцію спробувати створити іншу струну як

String string2 = "Hello";

JVM перевіряє, чи існує будь-який об'єкт String з постійною ціною в постійному пулі String, якщо так, а не робити абсолютно новий об'єкт JVM призначає посилання існуючого об'єкта на нову змінну.

І коли ми зберігаємо String як

String string = new String("Hello");

за допомогою нового ключового слова робиться абсолютно новий об’єкт із заданою ціною, незалежно від вмісту пулу постійних рядків.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.