[java] Java에서 final로 선언 된 ==와 문자열 비교

Java의 문자열에 대한 간단한 질문이 있습니다. 다음 간단한 코드 세그먼트는 두 문자열을 연결 한 다음와 비교합니다 ==.

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

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

비교 표현 concat=="string"반환 false명백한로 (I 사이의 차이 이해 equals()==).


이 두 문자열이 final이렇게 선언 되면

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

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

concat=="string"이 경우 비교식이 반환 true됩니다. 왜 final차이가 있습니까? 인턴 풀과 관련이 있습니까? 아니면 잘못 인도되고 있습니까?



답변

String( 불변 인 ) 변수를로 선언하고 final컴파일 타임 상수 표현식으로 초기화하면 컴파일 타임 상수 표현식이되고 값은 사용되는 컴파일러에 의해 인라인됩니다. 따라서 두 번째 코드 예제에서 값을 인라인 한 후 문자열 연결은 컴파일러에 의해 다음과 같이 변환됩니다.

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

문자열 리터럴이 삽입 되어 있기 때문에 비교할 때 "string"제공됩니다 .true

에서 §4.12.4 JLS – final변수 :

프리미티브 유형 또는 유형의 변수 String, 즉 final컴파일 타임 상수 표현식 (§15.28)으로 초기화 된 변수를 상수 변수 라고 합니다 .

또한 JLS §15.28-상수 표현 :

형식의 컴파일 타임 상수 식은 메서드를 사용하여 고유 한 인스턴스를 공유하기 위해 String항상 “인터 닝” 됩니다 String#intern().


String변수가 아닌 첫 번째 코드 예제에서는 그렇지 않습니다 final. 따라서 이들은 컴파일 타임 상수 표현식이 아닙니다. 연결 작업은 런타임까지 지연되어 새 String객체 가 생성 됩니다. 두 코드의 바이트 코드를 비교하여이를 확인할 수 있습니다.

첫 번째 코드 예제 (비 final버전) 는 다음 바이트 코드로 컴파일됩니다.

  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

분명히 두 개의 개별 변수를 저장 str하고 연결 작업을 수행하는 데 사용하고 있습니다.ingStringBuilder

반면 두 번째 코드 예제 ( final버전) 는 다음과 같습니다.

  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

따라서 string컴파일 타임에 문자열을 작성하기 위해 최종 변수를 직접 인라인하여 ldc단계의 조작으로 로드됩니다 0. 그런 다음 두 번째 문자열 리터럴이 ldc단계에서 작업에 의해로드됩니다 7. String런타임에 새로운 객체를 생성하지는 않습니다 . 문자열은 컴파일 타임에 이미 알려져 있으며 인턴됩니다.


답변

내 연구에 따르면 모든 것이 final StringJava로 인턴되었습니다. 블로그 게시물 중 하나에서 :

따라서 == 또는! =를 사용하여 두 개의 String을 비교해야하는 경우 비교하기 전에 String.intern () 메서드를 호출해야합니다. 그렇지 않으면 항상 문자열 비교를 위해 String.equals (String)를 선호하십시오.

따라서 호출 String.intern()하면 ==연산자를 사용하여 두 문자열을 비교할 수 있습니다 . 그러나 String.intern()Java final String에서는 내부적으로 인턴 되기 때문에 여기 에 필요하지 않습니다 .

String.intern () 메소드에 == 연산자 와 Javadoc을 사용하여 자세한 정보를 비교할 수 있습니다 .

자세한 내용 은이 Stackoverflow 게시물을 참조하십시오.


답변

이 방법을 살펴보면

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

그리고 javap -c ClassWithTheseMethods
당신이 볼 수 있는 버전 으로 디 컴파일

  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;
      ...

  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
       ...

문자열이되지 않도록 경우 최종 컴파일러는 사용해야 할 것입니다 StringBuilder연결하는 str1str2그래서

String concat=str1+str2;

에 컴파일됩니다

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

concat, 런타임에 생성되므로 문자열 풀에서 제공되지 않습니다.


또한 문자열이 최종인 경우 컴파일러는 사용하지 않고 변경하지 않는다고 가정 할 수 있으므로 사용 StringBuilder하는 대신 안전하게 값을 연결할 수 있습니다.

String concat = str1 + str2;

로 변경할 수 있습니다

String concat = "str" + "ing";

에 연결

String concat = "string";

이는 concate문자열 풀에 삽입되어 if명령문의 해당 풀에서 동일한 문자열 리터럴과 비교되는 스팅 리터럴이 됨을 의미합니다 .


답변

스택 및 문자열 계속 풀 개념
여기에 이미지 설명을 입력하십시오


답변

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
}

0:2:String "string" (상수 풀)를 스택으로 푸시 및 로컬 변수에 저장 concat직접. 컴파일러가 String "string"컴파일 타임에 자체를 생성 (연결)하고 있다고 추론 할 수 있습니다 .

final 바이트 코드

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
}

여기에 두 가지가 String상수를 "str"하고 "ing"있는은과 런타임에 연결된해야합니다 StringBuilder.


답변

Java의 String 리터럴 표기법을 사용하여 만들 때 해당 객체가 풀에없는 경우 자동으로 intern () 메서드를 호출하여 해당 객체를 String 풀에 넣습니다.

final은 왜 차이가 있습니까?

컴파일러 는 최종 변수가 절대 변경되지 않음을 알고 있습니다.이 최종 변수를 추가하면 str1 + str2표현식 출력으로 인해 출력이 문자열 풀로 이동합니다. 결과 출력도 변경되지 않으므로 결국 컴파일러는 위의 두 최종 변수의 출력 후에 인터 메소드를 호출합니다. 최종 변수가 아닌 변수 컴파일러의 경우 인턴 메소드를 호출하지 마십시오.


답변