W jaki sposób klasa String zastępuje operator +?

130

Dlaczego w Javie możesz dodawać ciągi za pomocą operatora +, kiedy String jest klasą? W String.javakodzie nie znalazłem żadnej implementacji dla tego operatora. Czy ta koncepcja narusza orientację na obiekt?

Pooya
źródło
22
+Operator plus ( ) to funkcja języka Java.
adatapost
19
To wszystko magia kompilatora. Nie możesz przeładowywać operatorów w Javie.
Lion,
19
Myślę, że dziwne jest to, że String jest zaimplementowany jako biblioteka poza językiem (w java.lang.String), ale ma specyficzne wsparcie wewnątrz języka. To nie w porządku.
13ren
@ 13ren mylisz się. Ciąg jest poza klasą java.lang, która oznacza język - java lanaguage !!! Wszystkie zajęcia w tym pakiecie są wyjątkowe. Zauważ, że nie musisz importować java.lang, aby ich używać.

Odpowiedzi:

158

Przyjrzyjmy się następującym prostym wyrażeniom w Javie

int x=15;
String temp="x = "+x;

Nawróceni kompilatora "x = "+x;język StringBuilderwewnętrznie i zastosowań .append(int)„dodać” liczbę całkowitą na łańcuch.

5.1.11. Konwersja ciągów

Każdy typ można przekonwertować na typ String przez konwersję ciągów.

Wartość x typu pierwotnego T jest najpierw konwertowana na wartość referencyjną, tak jakby przez podanie jej jako argumentu do odpowiedniego wyrażenia tworzenia instancji klasy (§ 15.9):

  • Jeśli T jest wartością logiczną, użyj new Boolean (x).
  • Jeśli T jest char, użyj nowego znaku (x).
  • Jeśli T jest bajtowe, krótkie lub int, użyj new Integer (x).
  • Jeśli T jest długie, użyj nowego Long (x).
  • Jeśli T jest zmiennoprzecinkowe, użyj nowego Float (x).
  • Jeśli T jest podwójne, użyj nowego Double (x).

Ta wartość odniesienia jest następnie konwertowana na typ String przez konwersję ciągu.

Teraz należy wziąć pod uwagę tylko wartości odniesienia:

  • Jeśli odwołanie ma wartość null, jest konwertowane na łańcuch „null” (cztery znaki ASCII n, u, l, l).
  • W przeciwnym razie konwersja jest wykonywana tak, jakby przez wywołanie metody toString przywoływanego obiektu bez argumentów; ale jeśli wynik wywołania metody toString ma wartość null, zamiast tego używany jest ciąg „null”.

Metoda toString jest zdefiniowana przez pierwotną klasę Object (§4.3.2). Wiele klas go zastępuje, w szczególności Boolean, Character, Integer, Long, Float, Double i String.

Szczegółowe informacje na temat kontekstu konwersji ciągów można znaleźć w §5.4.

15.18.1.

Optymalizacja konkatenacji ciągów: implementacja może zdecydować się na wykonanie konwersji i konkatenacji w jednym kroku, aby uniknąć tworzenia, a następnie odrzucania pośredniego obiektu String. Aby zwiększyć wydajność wielokrotnego łączenia ciągów, kompilator języka Java może użyć klasy StringBuffer lub podobnej techniki w celu zmniejszenia liczby pośrednich obiektów String, które są tworzone przez ocenę wyrażenia.

W przypadku typów pierwotnych implementacja może również zoptymalizować tworzenie obiektu otoki, konwertując bezpośrednio z typu pierwotnego na ciąg.

Zoptymalizowana wersja nie wykona najpierw pełnej konwersji napisów.

To jest dobra ilustracja zoptymalizowanej wersji używanej przez kompilator, aczkolwiek bez konwersji prymitywu, gdzie można zobaczyć, jak kompilator zmienia rzeczy w StringBuilder w tle:

http://caprazzi.net/posts/java-bytecode-string-concatenation-and-stringbuilder/


Ten kod java:

public static void main(String[] args) {
    String cip = "cip";
    String ciop = "ciop";
    String plus = cip + ciop;
    String build = new StringBuilder(cip).append(ciop).toString();
}

Generuje to - zobacz, jak dwa style konkatenacji prowadzą do tego samego kodu bajtowego:

 L0
    LINENUMBER 23 L0
    LDC "cip"
    ASTORE 1
   L1
    LINENUMBER 24 L1
    LDC "ciop"
    ASTORE 2

   // cip + ciop

   L2
    LINENUMBER 25 L2

    NEW java/lang/StringBuilder
    DUP
    ALOAD 1
    INVOKESTATIC java/lang/String.valueOf(Ljava/lang/Object;)Ljava/lang/String;
    INVOKESPECIAL java/lang/StringBuilder.<init>(Ljava/lang/String;)V
    ALOAD 2
    INVOKEVIRTUAL java/lang/StringBuilder.append(Ljava/lang/String;)Ljava/lang/StringBuilder;
    INVOKEVIRTUAL java/lang/StringBuilder.toString()Ljava/lang/String;

    ASTORE 3

    // new StringBuilder(cip).append(ciop).toString()

   L3
    LINENUMBER 26 L3

    NEW java/lang/StringBuilder
    DUP
    ALOAD 1
    INVOKESPECIAL java/lang/StringBuilder.<init>(Ljava/lang/String;)V
    ALOAD 2
    INVOKEVIRTUAL java/lang/StringBuilder.append(Ljava/lang/String;)Ljava/lang/StringBuilder;
    INVOKEVIRTUAL java/lang/StringBuilder.toString()Ljava/lang/String;

    ASTORE 4
   L4
    LINENUMBER 27 L4
    RETURN

Patrząc na powyższy przykład i jak generowany jest kod bajtowy oparty na kodzie źródłowym w podanym przykładzie, będziesz mógł zauważyć, że kompilator wewnętrznie przekształcił następującą instrukcję

cip+ciop; 

w

new StringBuilder(cip).append(ciop).toString();

Innymi słowy, operator +w konkatenacji ciągów znaków jest w rzeczywistości skrótem dla bardziej szczegółowego StringBuilderidiomu.

Lew
źródło
3
Wielkie dzięki, nie znam kodów bajtów jvm, ale wygenerowałem kod dla String plus = cip + ciop; and String build = new StringBuilder (cip) .append (ciop) .toString (); Są takie same. a moje pytanie brzmi, czy ta operacja narusza orientację obiektową?
Pooya,
7
Nie, to nieprawda. Przeciążanie operatorów (podobnie jak w C ++ i niektórych językach) ma pewne wady i projektanci Javy uznali, że jest to nieco zagmatwana koncepcja i pominęli ją w Javie. Dla mnie język zorientowany obiektowo musi mieć podstawowe koncepcje dziedziczenia, polimorfizmu i hermetyzacji, które ma Java.
Lion,
2
tak, ale myślę, że ten operator przeładował się dla klasy String
Pooya
3
Tak, przeciążanie operatorów jest używane w Javie do konkatenacji typu String, jednak nie można zdefiniować własnego operatora (jak w C ++, C # i niektórych innych językach).
Lion
5
@Pooya: w rzeczywistości "int / int" kontra "int / float" już jest przeciążeniem operatora, więc nawet C to ma. Tym, czego jednak nie ma C (i Java) , jest przeciążanie operatorów zdefiniowanych przez użytkownika: jedyną rzeczą, która definiuje różne sposoby użycia operatora (zarówno w C, jak i Javie), jest definicja języka (a rozróżnienie jest zaimplementowane w kompilator). C ++ różni się tym, że umożliwia przeciążanie operatora zdefiniowanego przez użytkownika (często nazywane po prostu „przeciążaniem operatora”).
Joachim Sauer
27

Jest to funkcja kompilatora Java, która sprawdza operandy +operatora. Na podstawie operandów generuje kod bajtowy:

  • W przypadku String generuje kod do łączenia ciągów
  • W przypadku Numbers generuje kod służący do dodawania liczb.

Oto, co mówi specyfikacja Java :

Operatory + i -nazywane są operatorami addytywnymi. AdditiveExpression: MultiplicativeExpression AdditiveExpression + MultiplicativeExpression AdditiveExpression - MultiplicativeExpression

Operatory addytywne mają ten sam priorytet i są syntaktycznie lewostronnie asocjacyjne (grupują od lewej do prawej). Jeśli typ dowolnego operandu +operatora to String, operacją jest konkatenacja łańcuchów.

W przeciwnym razie typ każdego z operandów +operatora musi być typem, który można zamienić (§5.1.8) na pierwotny typ liczbowy lub wystąpi błąd w czasie kompilacji.

W każdym przypadku typ każdego -operandu operatora binarnego musi być typem konwertowalnym (§5.1.8) na pierwotny typ liczbowy lub wystąpi błąd w czasie kompilacji.

Ramesh PVK
źródło
7
Cytat ze specyfikacji w ogóle nie dotyczy tego pytania.
Ernest Friedman-Hill
To jest wyciąg "Jeśli typ dowolnego operandu operatora + to String, operacją jest konkatenacja łańcuchów. W przeciwnym razie typ każdego operandu operatora + musi być typem konwertowalnym (§5.1.8 ) na pierwotny typ liczbowy lub wystąpi błąd w czasie kompilacji ”. Czy możesz mi powiedzieć, dlaczego nie jest to istotne.
Ramesh PVK
7
Nie mówi nic o tym, jak jest zaimplementowany - oto jest pytanie. Myślę, że plakat już rozumie, że ta funkcja istnieje.
Ernest Friedman-Hill,
14

W jaki sposób klasa String zastępuje operator +?

Tak nie jest. Kompilator to robi. Ściśle mówiąc, kompilator przeciąża operator + dla operandów typu String.

Markiz Lorne
źródło
6

Przede wszystkim (+) jest przeciążony, a nie nadpisany

Język Java zapewnia specjalną obsługę operatora konkatenacji ciągów (+), który został przeciążony dla obiektów Java Strings.

  1. Jeśli operandem po lewej stronie jest String, działa on jako konkatenacja.

  2. Jeśli operand po lewej stronie jest liczbą całkowitą, działa jako operator dodawania

Pramod Kumar
źródło
3
(2) Jeśli lewy operand jest liczbą całkowitą, jest on automatycznie rozpakowywany inti wtedy mają zastosowanie normalne reguły języka Java.
Markiz Lorne,
2
Dwie reguły podane poniżej cudzysłowu są błędne: uważam, że powinny to być: dwa elementy pierwotne (lub klasy niepowiązane) = dodawanie; co najmniej jeden łańcuch = konkatenacja
mwfearnley
4

Język Java zapewnia specjalną obsługę operatora konkatenacji ciągów (+) oraz konwersji innych obiektów na łańcuchy. Konkatenacja ciągów jest implementowana za pomocą klasy StringBuilder(lub StringBuffer) i jej appendmetody.

Jigar Pandya
źródło
4

Znaczenie +operatora w zastosowaniu do Stringjest definiowane przez język, tak jak wszyscy już napisali. Ponieważ wydaje Ci się, że nie jest to wystarczająco przekonujące, rozważ to:

Ints, float i double mają różne reprezentacje binarne, dlatego dodanie dwóch liczb całkowitych jest inną operacją, jeśli chodzi o manipulację bitami, niż dodanie dwóch liczb zmiennoprzecinkowych: W przypadku liczb całkowitych można dodawać bit po bicie, przenosząc bit i sprawdzając, czy nie ma przepełnienia; w przypadku pływaków musisz osobno zająć się mantysami i wykładnikami.

Zatem w zasadzie „dodawanie” zależy od natury „dodawanych” obiektów. Java definiuje to dla ciągów znaków, a także dla int i float (długie, podwójne, ...)

alexis
źródło
3

+Operator zwykle zastąpione StringBuilderw czasie kompilacji. Sprawdź tę odpowiedź, aby uzyskać więcej informacji na ten temat.

Skorpion
źródło
Jeśli tak jest, czy istnieje jakikolwiek powód, dla którego StringBuilder w ogóle istnieje do użytku publicznego? Czy są jakieś przypadki, w których +operator nie jest zastępowany przez StringBuilder?
Cemre,
2
Pytanie, które chcesz zadać, brzmi: „dlaczego operator + w ogóle istnieje do użytku publicznego?”, Ponieważ to jest tutaj ohydne. Co do drugiego pytania, nie wiem dokładnie, ale myślę, że nie ma takiego przypadku.
Scorpio
Kompilator może zamiast tego użyć concat (), jeśli są tylko dwa elementy. Kompilator nie może również zamienić concat () na StringBuilder (lub używa wielu StringBuilderów i dołącza je razem), gdy programista tworzy ciąg znaków w długim zagnieżdżonym / zapętlonym kodzie - użycie pojedynczego, jawnego elementu StringBuilder będzie lepsze dla wydajności.
user158037