2011-09-10 8 views
6

Mam następujący kod Java:Ile String obiekty będą tworzone

public String makinStrings() { 
    String s = "Fred"; 
    s = s + "47"; 
    s = s.substring(2, 5); 
    s = s.toUpperCase(); 
    return s.toString(); 
} 

Pytanie brzmi jakoś prosta: ilu String obiekty będą tworzone, gdy ta metoda jest wywoływana?

Na początku odpowiedziałem, że 5 obiektów String jest tworzonych, ale odpowiedź z mojej książki mówi, że tylko 3 obiekty są tworzone i nie podano żadnego wyjaśnienia (to jest pytanie SCJP).

Z mojego punktu widzenia jest 5 obiektów: "Fred", "47", "Fred47", "ed4", "ED4".

znalazłem też to pytanie na SCJP symulacji egzaminu, z tą samą odpowiedź 3.

+3

sobie wyobrazić, kompilator będzie inline pierwsze dwa oświadczenia i usunąć ostatni (redundantne) wywołanie metody. To pozostawia Ci "Fred47", "ed4", "ED4". –

+1

@Jared 's' nie jest wyrażeniem stałym * w czasie kompilacji *, więc tak się nie dzieje. (Skompiluj kod i użyj 'javap -c' lub nawet' struny'.) –

+0

możliwy duplikat [Java - ile obiektów typu String?] (Http://stackoverflow.com/questions/17898236/java-how-many -string-objects) –

Odpowiedz

15

„Fred” i „47” będzie pochodzić z łańcucha dosłownym basenie. Jako takie, one nie będą tworzone, gdy zostanie wywołana metoda. Zamiast tego zostaną tam umieszczone, gdy klasa zostanie załadowana (lub wcześniej, jeśli inne klasy używają stałych o tej samej wartości).

"Fred47", "ed4" i "ED4" to obiekty 3 String, które zostaną utworzone przy każdym wywołaniu metody.

+0

Czy po pierwszym wywołaniu metody również "Fred47", "ed4" i "ED4" nie znajdą się w literalnej puli łańcuchowej? Wierzę, że odpowiedź/komentarz Jareda jest poprawna (+1), ponieważ wszystkie kompilatory, które znam, wykonują te proste inline. Dlatego łączenie łańcuchów nie jest już tak bardzo kosztowne (choć do pewnego stopnia). – DaveFar

+1

Jest to [pozornie legalne] (http://java.sun.com/docs/books/vmspec/2nd-edition/html/Concepts.doc.html#19124) dla JVM do tworzenia instancji ciągów znaków, gdy są one ładowane po raz pierwszy ze stałej puli, zamiast ładować je z łatwością w czasie ładowania klasy. Nie sądzę, że ten szczegół implementacji jest naprawdę istotny w stosunku do pierwotnego pytania, ale jest to ciekawy kawałek ciekawostek. –

+0

Uwaga: jeśli pierwsze dwa wiersze zostały napisane jako pojedyncza instrukcja, "Fred47" będzie użyty bezpośrednio jako '" Fred "+" 47 "' jest * stałym wyrażeniem czasu kompilacji *. Możliwe jest również, że biblioteka jest inaczej zaimplementowana i tworzy inną liczbę obiektów typu string. –

2

Programy zwykle zawierają dużo literałów String w swoim kodzie. W języku Java te stałe są zbierane w coś o nazwie Tabela ciągów dla zwiększenia wydajności. Na przykład, jeśli używasz ciągu "Name: " w dziesięciu różnych miejscach, JVM (zazwyczaj) ma tylko jedną instancję tego ciągu i we wszystkich dziesięciu miejscach, w których jest używany, wszystkie odwołania wskazują na to jedno wystąpienie. To oszczędza pamięć.

Ta optymalizacja jest możliwa, ponieważ String jest niezmienna niezmienna. Gdyby można było zmienić łańcuch, zmiana go o jedno oznaczałoby zmianę również w pozostałych dziewięciu. Dlatego każda operacja, która zmienia łańcuch, zwraca nową instancję. Dlatego, jeśli to zrobić:

String s = "boink"; 
s.toUpperCase(); 
System.out.println(s); 

drukuje boink, nie BOINK.

Teraz jeszcze jedna trudne bit: wiele wystąpień java.lang.Stringmoże punktu do tego samego bazowego char[] dla swoich danych znakowych, innymi słowy, mogą one być różne widoki na tym samym char[], przy użyciu tylko kawałek tablicy. Ponownie optymalizacja wydajności. Metoda substring() jest jednym z przypadków, w których tak się dzieje.

s1 = "Fred47"; 

//String s1: data=[ 'F', 'r', 'e', 'd', '4', '7'], offset=0, length=6 
//     ^........................^ 

s2 = s1.substring(2, 5); 

//String s2: data=[ 'F', 'r', 'e', 'd', '4', '7'], offset=2, length=3 
//        ^.........^ 
// the two strings are sharing the same char[]! 

W swojej SCJP pytanie, to wszystko sprowadza się do:

  • Ciąg "Fred" pobierany jest z tabeli String.
  • Ciąg "47" pochodzi z tabeli Ciąg.
  • Łańcuch "Fred47" jest tworzony podczas wywołania metody.// 1
  • Łańcuch "ed4" jest tworzony podczas wywołania metody mają ten sam układ spodniego "Fred47" // 2
  • Łańcuch "ED4" utworzonego podczas wywołania metody. // 3
  • s.toString() nie tworzy nowego, po prostu zwraca this.

Jeden ciekawy przypadek krawędź tego wszystkiego: rozważyć, co się stanie, jeśli masz naprawdę długi ciąg, na przykład, strona internetowa pobrane z Internetu, powiedzmy długość char[] jest dwa megabajty. Jeśli weźmiesz substring(0, 4) z tego, otrzymasz nowy Ciąg, który wygląda, jakby miał tylko cztery znaki, ale nadal udostępnia te dwa megabajty danych z kopii zapasowej. To nie jest tak powszechne w świecie rzeczywistym, ale może to być ogromna strata pamięci! W (rzadkim) przypadku, w którym napotkasz to jako problem, możesz użyć new String(hugeString.substring(0, 4)), aby utworzyć ciąg z nową, małą tablicą backingową.

Wreszcie, możliwe jest wymuszenie String na tabeli strun w czasie wykonywania, dzwoniąc pod numer intern(). Podstawowa zasada w tym przypadku: nie rób tego. Rozszerzona reguła: nie rób tego, chyba że użyłeś profilera pamięci, aby upewnić się, że jest to użyteczna optymalizacja.

+0

To jest nieprawidłowe. Żadna ze stałych String nie pochodzi z "String Pool", wszystkie pochodzą z puli stałych klas. Jeśli ta sama literalna stała jak "Fred47" jest używana w kilku klasach, każda z nich ma stałą stałą w puli stałej. Zobacz bajt kodu odpowiedzi Chipa McCormicka. –

2

Na podstawie wyjścia javap wygląda na to, że podczas konkatencji tworzony jest StringBuilder, a nie String. Istnieją wtedy trzy ciągi wywoływane dla substring(), toUpperCase() i toString().

Ostatnie wywołanie nie jest nadmiarowe, ponieważ przekształca StringBuilder na ciąg.

>javap -c Test 
Compiled from "Test.java" 

public java.lang.String makinStrings(); 
Code: 
0: ldc  #5; //String Fred 
2: astore_1 
3: new  #6; //class java/lang/StringBuilder 
6: dup 
7: invokespecial #7; //Method java/lang/StringBuilder."<init>":()V 
10: aload_1 
11: invokevirtual #8; //Method java/lang/StringBuilder.append: (Ljava/lang/String;)Ljava/lang/StringBuilder; 
14: ldc  #9; //String 47 
16: invokevirtual #8; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 
19: invokevirtual #10; //Method java/lang/StringBuilder.toString:()Ljava/lang/String; 
22: astore_1 
23: aload_1 
24: iconst_2 
25: iconst_5 
26: invokevirtual #11; //Method java/lang/String.substring:(II)Ljava/lang/String; 
29: astore_1 
30: aload_1 
31: invokevirtual #12; //Method java/lang/String.toUpperCase:()Ljava/lang/String; 
34: astore_1 
35: aload_1 
36: invokevirtual #13; //Method java/lang/String.toString:()Ljava/lang/String; 
39: areturn 

}

+0

Tak, więc poprawna odpowiedź brzmi: 3. Czy jestem w błędzie? –

Powiązane problemy