Porównywanie łańcuchów z==, które są deklarowane jako końcowe w Javie

Mam proste pytanie o stringi w Javie. Poniższy segment prostego kodu łączy dwa ciągi znaków, a następnie porównuje je z ==.

String str1="str";
String str2="ing";
String concat=str1+str2;

System.out.println(concat=="string");

Wyrażenie porównawcze concat=="string" zwraca false jako oczywiste(Rozumiem różnicę między equals() i ==).


Gdy te dwa łańcuchy są zadeklarowane final w ten sposób,

final String str1="str";
final String str2="ing";
String concat=str1+str2;

System.out.println(concat=="string");

Wyrażenie porównawcze concat=="string", w tym przypadku zwraca true. Dlaczego final robi różnicę? Czy to musi zrobić coś z pulą stażystów, czy jestem po prostu wprowadzany w błąd?

Author: Rohit Jain, 2013-10-17

6 answers

Kiedy zadeklarujesz zmienną String (która jest niezmienna) jako final i zainicjalizujesz ją wyrażeniem stałej czasu kompilacji, staje się ona również wyrażeniem stałej czasu kompilacji, a jej wartość jest inlinowana przez kompilator, w którym jest używana. Tak więc, w drugim przykładzie kodu, po wpisaniu wartości, konkatenacja łańcucha jest tłumaczona przez kompilator na:

String concat = "str" + "ing";  // which then becomes `String concat = "string";`

Które w porównaniu do "string" da ci true, ponieważ literały ciągu są internowany.

Z JLS §4.12.4 - final zmienne :

Zmienna typu prymitywnego lub typu String, czyli {[4] } i zainicjalizowana wyrażeniem stałej czasu kompilacji (§15.28), nazywana jest zmienną stałą.

Również z JLS §15.28-wyrażenie stałe:

Wyrażenia stałe czasu kompilacji typu String są zawsze "internowane" tak, aby współdzielić unikalne instancje, używając metoda String#intern().


Tak nie jest w pierwszym przykładzie kodu, gdzie zmienne String nie są final. Nie są więc wyrażeniami stałymi w czasie kompilacji. Operacja konkatenacji będzie opóźniona do czasu uruchomienia, co doprowadzi do wytworzenia nowego obiektu String. Można to sprawdzić porównując kod bajtowy obu kodów.

Pierwszy przykład kodu (wersja nie-final) jest kompilowany do następującego bajtu kod:

  Code:
   0:   ldc     #2; //String str
   2:   astore_1
   3:   ldc     #3; //String ing
   5:   astore_2
   6:   new     #4; //class java/lang/StringBuilder
   9:   dup
   10:  invokespecial   #5; //Method java/lang/StringBuilder."<init>":()V
   13:  aload_1
   14:  invokevirtual   #6; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
   17:  aload_2
   18:  invokevirtual   #6; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
   21:  invokevirtual   #7; //Method java/lang/StringBuilder.toString:()Ljava/lang/String;
   24:  astore_3
   25:  getstatic       #8; //Field java/lang/System.out:Ljava/io/PrintStream;
   28:  aload_3
   29:  ldc     #9; //String string
   31:  if_acmpne       38
   34:  iconst_1
   35:  goto    39
   38:  iconst_0
   39:  invokevirtual   #10; //Method java/io/PrintStream.println:(Z)V
   42:  return

Najwyraźniej przechowuje str i ing w dwóch oddzielnych zmiennych i używa StringBuilder do wykonania operacji konkatenacji.

Natomiast drugi przykład kodu (final wersja) wygląda tak:

  Code:
   0:   ldc     #2; //String string
   2:   astore_3
   3:   getstatic       #3; //Field java/lang/System.out:Ljava/io/PrintStream;
   6:   aload_3
   7:   ldc     #2; //String string
   9:   if_acmpne       16
   12:  iconst_1
   13:  goto    17
   16:  iconst_0
   17:  invokevirtual   #4; //Method java/io/PrintStream.println:(Z)V
   20:  return

Więc bezpośrednio w linii końcowej zmiennej utworzyć łańcuch string w czasie kompilacji, który jest ładowany przez ldc operacji w kroku 0. Następnie drugi literał jest ładowany przez operację ldc w kroku 7. Nie obejmuje tworzenie dowolnego nowego obiektu String w czasie wykonywania. Łańcuch jest już znany w czasie kompilacji i są one internowane.

 228
Author: Rohit Jain,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-04-10 04:58:14

Z moich badań wynika, że wszyscy final String są internowani w Javie. Z jednego z wpisów na blogu:

Więc, jeśli naprawdę potrzebujesz porównać dwa ciąg znaków za pomocą = = or != upewnij się, że wywołujesz String.metoda intern () przed dokonaniem porównania. W przeciwnym razie zawsze preferuj ciąg.equals (String) dla porównania łańcuchów.

Oznacza to, że wywołując String.intern() można porównać dwa ciągi znaków za pomocą operatora ==. Ale tutaj String.intern() nie jest konieczne, ponieważ w Javie final String są wewnętrznie internowane.

Ty można znaleźć więcej informacji porównywanie łańcuchów za pomocą = = operator i Javadoc dla łańcuchów.intern () metoda.

Aby uzyskać więcej informacji, zapoznaj się również z tym postem Stoskoverflow .
 31
Author: Pradeep Simha,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:18:20

Jeśli przyjrzysz się tej metodzie

public void noFinal() {
    String str1 = "str";
    String str2 = "ing";
    String concat = str1 + str2;

    System.out.println(concat == "string");
}

public void withFinal() {
    final String str1 = "str";
    final String str2 = "ing";
    String concat = str1 + str2;

    System.out.println(concat == "string");
}

I jego dekompilacja z javap -c ClassWithTheseMethods wersje, które zobaczysz

  public void noFinal();
    Code:
       0: ldc           #15                 // String str
       2: astore_1      
       3: ldc           #17                 // String ing
       5: astore_2      
       6: new           #19                 // class java/lang/StringBuilder
       9: dup           
      10: aload_1       
      11: invokestatic  #21                 // Method java/lang/String.valueOf:(Ljava/lang/Object;)Ljava/lang/String;
      14: invokespecial #27                 // Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V
      17: aload_2       
      18: invokevirtual #30                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      21: invokevirtual #34                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      ...

I

  public void withFinal();
    Code:
       0: ldc           #15                 // String str
       2: astore_1      
       3: ldc           #17                 // String ing
       5: astore_2      
       6: ldc           #44                 // String string
       8: astore_3      
       ...

Więc jeśli ciągi nie są końcowymi kompilatorami będzie musiał użyć {[9] } do konkatenacji str1 i str2 więc

String concat=str1+str2;

Zostanie skompilowana do

String concat = new StringBuilder(str1).append(str2).toString();

Co oznacza, że concat zostanie utworzony w czasie wykonywania, Więc nie będzie pochodzić z puli łańcuchów.


Również jeśli łańcuchy są ostateczne, kompilator może założyć, że nigdy Zmień tak, aby zamiast używać StringBuilder mógł bezpiecznie łączyć swoje wartości tak

String concat = str1 + str2;

Można zmienić na

String concat = "str" + "ing";

I konkatenowany na

String concat = "string";

Co oznacza, że concate stanie się literał Stinga, który zostanie internowany w Puli łańcuchów, a następnie porównany z tym samym literałem łańcuchowym z tej puli w instrukcji if.

 21
Author: Pshemo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-03-09 15:31:18

Stack and string conts pool concept Tutaj wpisz opis obrazka

 14
Author: pnathan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-10-17 06:33:14

Zobaczmy kod bajtowy dla przykładu final

Compiled from "Main.java"
public class Main {
  public Main();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]) throws java.lang.Exception;
    Code:
       0: ldc           #2                  // String string
       2: astore_3
       3: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
       6: aload_3
       7: ldc           #2                  // String string
       9: if_acmpne     16
      12: iconst_1
      13: goto          17
      16: iconst_0
      17: invokevirtual #4                  // Method java/io/PrintStream.println:(Z)V
      20: return
}

At 0: and 2:, the String "string" jest wepchnięty na stos (ze stałej Puli) i przechowywany bezpośrednio w zmiennej lokalnej concat. Można wywnioskować, że kompilator tworzy (konkatenuje) String "string" się w czasie kompilacji.

Kod nie final bajtowy

Compiled from "Main2.java"
public class Main2 {
  public Main2();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]) throws java.lang.Exception;
    Code:
       0: ldc           #2                  // String str
       2: astore_1
       3: ldc           #3                  // String ing
       5: astore_2
       6: new           #4                  // class java/lang/StringBuilder
       9: dup
      10: invokespecial #5                  // Method java/lang/StringBuilder."<init>":()V
      13: aload_1
      14: invokevirtual #6                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/Stri
ngBuilder;
      17: aload_2
      18: invokevirtual #6                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/Stri
ngBuilder;
      21: invokevirtual #7                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      24: astore_3
      25: getstatic     #8                  // Field java/lang/System.out:Ljava/io/PrintStream;
      28: aload_3
      29: ldc           #9                  // String string
      31: if_acmpne     38
      34: iconst_1
      35: goto          39
      38: iconst_0
      39: invokevirtual #10                 // Method java/io/PrintStream.println:(Z)V
      42: return
}

Tutaj masz dwie stałe String, "str" i "ing", które muszą być połączone w czasie wykonywania z StringBuilder.

 3
Author: Sotirios Delimanolis,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-10-17 05:09:01

Jednak, gdy tworzysz w Javie dosłowną notację łańcuchową, automatycznie wywołuje ona metodę intern (), aby umieścić ten obiekt w Puli łańcuchów, pod warunkiem, że nie był on obecny w Puli.

Dlaczego final robi różnicę?

Kompilator wie, że ostateczna zmienna nigdy się nie zmieni, kiedy dodamy te końcowe zmienne, wyjście przechodzi do puli łańcuchów, ponieważ str1 + str2 wyjście wyrażenia również nigdy się nie zmieni, więc ostatecznie kompilator wywołuje metodę inter po wyjściu powyższych dwóch zmiennych końcowych. W przypadku kompilatora niekończących się zmiennych nie należy wywoływać metody intern.

 0
Author: Premraj,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-01-11 07:21:40