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
프리미티브 유형 또는 유형의 변수
String
, 즉final
컴파일 타임 상수 표현식 (§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
하고 연결 작업을 수행하는 데 사용하고 있습니다.ing
StringBuilder
반면 두 번째 코드 예제 ( 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 String
Java로 인턴되었습니다. 블로그 게시물 중 하나에서 :
따라서 == 또는! =를 사용하여 두 개의 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
연결하는 str1
및 str2
그래서
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
표현식 출력으로 인해 출력이 문자열 풀로 이동합니다. 결과 출력도 변경되지 않으므로 결국 컴파일러는 위의 두 최종 변수의 출력 후에 인터 메소드를 호출합니다. 최종 변수가 아닌 변수 컴파일러의 경우 인턴 메소드를 호출하지 마십시오.