Jaka jest różnica między „tekstem” a nowym ciągiem („tekstem”)?

195

Jaka jest różnica między tymi dwoma następującymi stwierdzeniami?

String s = "text";

String s = new String("text");
użytkownik368141
źródło
Temat pokrewny: JEP 192: Deduplikacja ciągów w G1 .
Basil Bourque,
Proszę o odpowiedź na to pytanie. Ciąg a = „Java”; Ciąg b = „Java”; System.out.println (a == b); true // ale System.out.println („a == b?” + a == b); // false ...
Energy
nie rozumiem, kiedy dodałem jakiś komentarz („a == b?) => mój wynik staje się FAŁSZ. dlaczego?
Energy
@Energy Wynik jest taki, falseże kolejność operacji dyktuje, że operator + idzie pierwszy, konkatenując „a == b?” za pomocą, aby utworzyć ciąg „a == b? Java”. Następnie wyrażenie ma "a==b?Java" == bwartość false.
Allison B
@AllisonB mam to, dziękuję bardzo!
Energia

Odpowiedzi:

187

new String("text"); wyraźnie tworzy nową i referencyjnie odrębną instancję Stringobiektu; String s = "text";może ponownie użyć instancji z stałej puli ciągów, jeśli jest ona dostępna.

Jesteś bardzo rzadko kiedykolwiek chcesz użyć new String(anotherString)konstruktora. Z interfejsu API:

String(String original): Inicjuje nowo utworzony String obiekt, aby reprezentował tę samą sekwencję znaków co argument; innymi słowy, nowo utworzony ciąg jest kopią ciągu argumentu. O ile nie jest wymagana wyraźna kopia oryginału, użycie tego konstruktora nie jest konieczne, ponieważ ciągi znaków są niezmienne.

Powiązane pytania


Co oznacza rozróżnienie referencyjne

Sprawdź następujący fragment kodu:

    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

==na dwóch typach referencyjnych jest porównanie tożsamości referencyjnej. Dwa obiekty, które equalsniekoniecznie są ==. Zwykle niewłaściwe jest użycie ==w odniesieniu do typów referencyjnych; przez większość czasu equalsnależy użyć zamiast tego.

Niemniej jednak, jeśli z jakiegoś powodu musisz utworzyć dwa equals, ale nie ==łańcuch, to można użyć new String(anotherString)konstruktora. Należy jednak powtórzyć, że jest to bardzo dziwne i rzadko jest to zamierzenie.

Bibliografia

Powiązane kwestie

środki smarujące wielotlenowe
źródło
3
Jeśli napiszę: String s = new String ("abc"); A teraz piszę: String s = "abc"; Will String s = "abc"; utworzyć nowy literał String w puli String?
Kaveesh Kanwal
Dlaczego nikt nie odpowiada na poprzednie pytanie?
zeds
2
@KaveeshKanwal Nie, literał nie zostanie skopiowany. Jak widać są 2 "abc"s. Tylko jeden z nich przejdzie do puli Ciągów, a drugi będzie się do niego odnosił. Potem sjest odpowiedni nowy obiekt.
Kayaman
1
@Kaveesh Kanwal - String s = new String („abc”) utworzy tylko nowy obiekt String o wartości „abc”. A druga instrukcja sprawdzi, czy dosłowny ciąg „abc” jest już obecny w puli ciągów, czy nie. Jeśli jest już obecny, to zwracane jest odwołanie do istniejącego, a jeśli nie, to tworzona jest nowa literał („abc”) w puli ciągów. Mam nadzieję, że to rozwiązuje twoje zapytanie !!
user968813
Nie ma w tym nic „może”. Kompilator musi pula literałów łańcuchowych. JLS 3.10.5 .
Markiz Lorne
119

Literały ciągów przejdą do puli stałej ciągów .

Poniższa migawka może pomóc ci zrozumieć to wizualnie, aby zapamiętać go na dłużej.

wprowadź opis zdjęcia tutaj


Tworzenie obiektu linia po linii:

String str1 = new String("java5");

Używając w konstruktorze dosłownego ciągu „java5”, nowa wartość ciągu jest przechowywana w stałej puli ciągów. Za pomocą nowego operatora tworzony jest nowy obiekt ciągu na stercie o wartości „java5”.

String str2 = "java5"

Odwołanie „str2” wskazuje na już zapisaną wartość w stałej puli ciągów

String str3 = new String(str2);

Nowy obiekt ciągu jest tworzony na stercie o tej samej wartości co odwołanie przez „str2”

String str4 = "java5";

Odwołanie „str4” wskazuje na już zapisaną wartość w stałej puli ciągów

Wszystkie obiekty: Kupa - 2, Pula - 1

Dalsza lektura na temat społeczności Oracle

Braj
źródło
1
Dobra odpowiedź .. ale chcemy wiedzieć, że teraz jestem giong, aby zmienić wartość str1 = "java6", to zmieni wartość str4?
CoronaPintu
2
tak, miałem sprawdzenie, że nie zmieni wartości str4
CoronaPintu
@Braj Czy możesz przedstawić dokumentację dotyczącą twierdzenia, na które udzielono odpowiedzi?
Basil Bourque,
@Braj: Czy nagłówki „Sterty” i „puli” w tabeli powinny być odwrócone?
Rahul Kurup,
Niepoprawne. Stała pula jest tworzona w czasie kompilacji, a nie w czasie wykonywania. Nie używaj formatowania cytatów dla tekstu, który nie jest cytowany.
Markiz Lorne
15

Jeden tworzy ciąg w puli stałej ciągów

String s = "text";

drugi tworzy ciąg w stałej puli ( "text"), a drugi ciąg w normalnej przestrzeni sterty ( s). Oba ciągi będą miały tę samą wartość, co „tekst”.

String s = new String("text");

s jest następnie tracony (kwalifikuje się do GC), jeśli później nie będzie używany.

Z drugiej strony literały łańcuchowe są ponownie wykorzystywane. Jeśli używasz "text"w wielu miejscach swojej klasy, w rzeczywistości będzie to jeden i tylko jeden Ciąg (tj. Wiele odniesień do tego samego ciągu w puli).


źródło
Ciągi w stałej puli nigdy nie są tracone. Czy miałeś na myśli, że „s” zostanie utracone, jeśli później nie będzie używane?
Markiz Lorne
@EJP: tak, miałem na myśli „s”. Dzięki za zauważenie. Poprawię pytanie.
9

JLS

JLS nazywa tę koncepcję „internowaniem”.

Odpowiedni fragment z JLS 7 3.10.5 :

Co więcej, literał łańcuchowy zawsze odnosi się do tego samego wystąpienia klasy Łańcuch. Wynika to z tego, że literały łańcuchowe - lub, bardziej ogólnie, łańcuchy, które są wartościami wyrażeń stałych (§ 15.28) - są „internowane”, aby dzielić unikalne instancje, przy użyciu metody String.intern.

Przykład 3.10.5-1. Literały smyczkowe

Program składający się z jednostki kompilacyjnej (§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"; }

i jednostka kompilacyjna:

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

daje wynik:

true true true true false true

JVMS

JVMS 7 5.1 mówi :

Literał łańcuchowy jest odwołaniem do instancji klasy Łańcuch i pochodzi ze struktury CONSTANT_String_info (§4.4.3) w binarnej reprezentacji klasy lub interfejsu. Struktura CONSTANT_String_info podaje sekwencję punktów kodowych Unicode stanowiących literał ciągu.

Język programowania Java wymaga, aby identyczne literały łańcuchowe (tj. Literały zawierające tę samą sekwencję punktów kodowych) musiały odnosić się do tej samej instancji klasy String (JLS §3.10.5). Ponadto, jeśli metoda String.intern zostanie wywołana na dowolnym ciągu, wynikiem jest odwołanie do tej samej instancji klasy, która zostałaby zwrócona, gdyby ten ciąg pojawił się jako literał. Dlatego poniższe wyrażenie musi mieć wartość true:

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

Aby uzyskać literał ciąg, wirtualna maszyna Java sprawdza sekwencję punktów kodu podaną przez strukturę CONSTANT_String_info.

  • Jeśli metoda String.intern została wcześniej wywołana na instancji klasy String zawierającej sekwencję punktów kodu Unicode identycznych z podanymi przez strukturę CONSTANT_String_info, to wynik wyprowadzenia literału łańcucha jest odniesieniem do tej samej instancji klasy String.

  • W przeciwnym razie tworzona jest nowa instancja klasy String zawierająca sekwencję punktów kodu Unicode podaną przez strukturę CONSTANT_String_info; odwołanie do tej instancji klasy jest wynikiem pochodnej literału łańcuchowego. Na koniec wywoływana jest metoda intern nowej instancji String.

Kod bajtowy

Warto również przyjrzeć się implementacji kodu bajtowego w OpenJDK 7.

Jeśli dekompilujemy:

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);
    }
}

mamy na stałej puli:

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

i 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

Uwaga jak:

  • 0i 3: ldc #2ładowana jest ta sama stała (literały)
  • 12: tworzona jest nowa instancja ciągu (za pomocą #2 argumentem jako)
  • 35: ai csą porównywane jako zwykłe obiekty zif_acmpne

Reprezentacja ciągów ciągłych jest dość magiczna w kodzie bajtowym:

a powyższy cytat JVMS wydaje się mówić, że ilekroć wskazany Utf8 jest taki sam, ładowane są identyczne instancje ldc.

Zrobiłem podobne testy dla pól i:

  • static final String s = "abc"wskazuje na stałą tabelę poprzez atrybut ConstantValue
  • pola nie-końcowe nie mają tego atrybutu, ale nadal można je zainicjować za pomocą ldc

Wniosek : istnieje bezpośrednia obsługa kodów bajtów dla puli ciągów, a reprezentacja pamięci jest wydajna.

Bonus: porównaj to z pulą liczb całkowitych , która nie ma bezpośredniego wsparcia dla kodu bajtowego (tzn. Nie ma CONSTANT_String_infoanalogu).

Ciro Santilli
źródło
2

@Braj: Myślę, że wspominałeś już na odwrót. Proszę, popraw mnie jeśli się mylę

Tworzenie obiektu linia po linii:

Ciąg str1 = nowy ciąg („java5”)

   Pool- "java5" (1 Object)

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

Ciąg str2 = "java5"

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

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

Ciąg str3 = nowy ciąg (str2)

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

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

Ciąg str4 = "java5"

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

  heap - str1 => "java5", str3 => "java5" (2 Objects)
SDC
źródło
str1nie bierze udziału w wartości str2lub str3czy str4w jakikolwiek sposób.
Markiz Lorne
1

Pomyśl o "bla"byciu magiczną fabryką jak Strings.createString("bla")(pseudo). Fabryka posiada pulę wszystkich utworzonych w ten sposób łańcuchów.

Jeśli zostanie wywołany, sprawdza, czy w puli jest już ciąg o tej wartości. Jeśli true, zwraca ten obiekt łańcucha, a zatem łańcuchy uzyskane w ten sposób są rzeczywiście tym samym obiektem.

Jeśli nie, tworzy nowy obiekt ciągu wewnętrznie, zapisuje go w puli, a następnie zwraca. W ten sposób, gdy następnym razem zapytanie o tę samą wartość ciągu jest zwracane, zwraca tę samą instancję.

Ręczne tworzenie new String("")zastępuje to zachowanie, omijając pulę literałów łańcuchowych. Dlatego zawsze należy sprawdzać równość za pomocą, equals()która porównuje sekwencję znaków zamiast równości odniesienia do obiektu.

b_erb
źródło
„Magiczna fabryka”, o której mówisz, jest niczym więcej niż kompilatorem Java. Błędem jest pisanie o tym procesie, tak jakby miał miejsce w czasie wykonywania.
Markiz Lorne
1

Jeden prosty sposób na zrozumienie różnicy znajduje się poniżej:

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");
        }

wyjście jest

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

Zatem nowa String () zawsze utworzy nową instancję.

Shashank T.
źródło
1

Dowolny literał łańcuchowy jest tworzony w puli literału łańcuchowego, a pula nie pozwala na żadne duplikaty. Zatem jeśli dwa lub więcej obiektów łańcuchowych zostanie zainicjowanych z tą samą wartością literału, wówczas wszystkie obiekty będą wskazywały na ten sam literał.

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

„obj1” i „obj2” będą wskazywać ten sam literał łańcuchowy, a pula literałów łańcuchowych będzie miała tylko jeden literał „abc”.

Kiedy tworzymy obiekt klasy String za pomocą nowego słowa kluczowego, utworzony w ten sposób ciąg jest przechowywany w pamięci sterty. Wszelkie literały łańcuchowe przekazywane jako parametr do konstruktora klasy String są jednak przechowywane w puli łańcuchów. Jeśli tworzymy wiele obiektów przy użyciu tej samej wartości z nowym operatorem, nowy obiekt będzie tworzony na stercie za każdym razem, ponieważ tego nowego operatora należy unikać.

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

„obj1” i „obj2” będą wskazywać na dwa różne obiekty na stercie, a pula literałów łańcuchowych będzie miała tylko jeden literał „abc”.

Warto również zwrócić uwagę na zachowanie łańcuchów, ponieważ każde nowe przypisanie lub konkatenacja na łańcuchu tworzy nowy obiekt w pamięci.

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

Teraz w powyższym przypadku:
Wiersz 1: literał „abc” jest przechowywany w puli ciągów.
Wiersz 2: literał „abcdef” zostaje zapisany w puli ciągów.
Wiersz 3: Nowy literał „xyz” jest przechowywany w puli ciągów, a „str1” zaczyna wskazywać na ten literał.
Wiersz 4: Ponieważ wartość jest generowana przez dołączenie do innej zmiennej, wynik jest przechowywany w pamięci sterty, a dosłownie dodawane „ghi” zostanie sprawdzone pod kątem istnienia w puli ciągów i zostanie utworzone, ponieważ nie istnieje w powyższy przypadek.

Amritansh
źródło
0

Chociaż wygląda tak samo z punktu widzenia programistów, ma duży wpływ na wydajność. Prawie zawsze chciałbyś skorzystać z pierwszej formy.

fastcodejava
źródło
0
String str = new String("hello")

Sprawdza, czy stała pula ciągów zawiera już ciąg „witaj”? Jeśli jest obecny, nie doda wpisu w puli stałej String. Jeśli nie jest obecny, doda pozycję w stałej puli ciągów.

Obiekt zostanie utworzony w obszarze pamięci sterty i strpunktami odniesienia do obiektu utworzonego w lokalizacji pamięci sterty.

jeśli chcesz strodwoływać się do obiektu punktowego zawierającego w stałej puli Ciąg, to musisz jawnie wywołaćstr.intern();

String str = "world";

Sprawdza, czy stała pula ciągów zawiera już ciąg „witaj”? Jeśli jest obecny, nie doda wpisu w puli stałej String. Jeśli nie jest obecny, doda pozycję w stałej puli ciągów.

W obu powyższych przypadkach strodwołanie wskazuje na ciąg "world"znaków w puli stałej.

Jayesh
źródło
„It” jest kompilatorem Java. Dosłowny ciąg tworzy unikalny wpis w stałej puli w czasie kompilacji. Błędem jest opisanie tego procesu tak, jakby miało to miejsce w czasie wykonywania.
Markiz Lorne z
Czy możesz wyjaśnić jasno, co jest nie tak w tym poście?
Jayesh
To, co jest złe w tym poście, to to, że literał ciągów jest zbierany w czasie kompletowania, jak już powiedziałem. Nie podczas wykonywania kodu, jak w twojej odpowiedzi.
Markiz Lorne
@EJP Doceniam twoją odpowiedź. Czy możesz wskazać dokładną linię, która jest błędna w odpowiedzi? Widzę, że wszystkie powyższe odpowiedzi są takie same, jak napisałem. Proszę o pomoc, chcę poprawić moje zrozumienie. Dzięki.
Jayesh
Pisałeś o całym procesie, tak jakby wszystko miało miejsce, gdy wiersz kodu jest wykonywany, co, jak wielokrotnie powtarzałem, nie jest prawdą. Nie możesz zredukować tego wszystkiego do jednej „dokładnej linii”, która jest błędna w twojej odpowiedzi.
Markiz Lorne
0

Gdy przechowujesz ciąg znaków jako

String string1 = "Hello";

bezpośrednio, następnie JVM tworzy obiekt String o podanej cenie podczas oddzielnego bloku pamięci o nazwie Pula stałych String.

I ilekroć mamy tendencję do próby wyprodukowania kolejnego ciągu jako

String string2 = "Hello";

JVM sprawdza, czy istnieje jakiś obiekt String ze stałą ceną w puli stałej String, jeśli tak, zamiast tworzyć nowy obiekt JVM przypisuje odwołanie istniejącego obiektu do nowej zmiennej.

A kiedy przechowujemy String jako

String string = new String("Hello");

przy użyciu nowego słowa kluczowego powstaje nowy obiekt o podanej cenie bez względu na zawartość stałej puli Ciąg.

Syed Babar Ali Shah 3261-FBASB
źródło