다음 두 진술의 차이점은 무엇입니까?
String s = "text";
String s = new String("text");
false
연산 순서가 + 연산자가 먼저 가고 "a == b?" 를 사용하여 문자열 "a == b? Java"를 만듭니다. 그런 다음 표현식 "a==b?Java" == b
은 false로 평가됩니다.
다음 두 진술의 차이점은 무엇입니까?
String s = "text";
String s = new String("text");
false
연산 순서가 + 연산자가 먼저 가고 "a == b?" 를 사용하여 문자열 "a == b? Java"를 만듭니다. 그런 다음 표현식 "a==b?Java" == b
은 false로 평가됩니다.
답변:
new String("text");
명시 적으로 새롭고 참조 가능한 String
객체 인스턴스를 작성 합니다. 사용 가능한 경우 문자열 상수 풀String s = "text";
에서 인스턴스를 재사용 할 수 있습니다.
당신은 아주 드물게 이제까지 사용하지 않으려는 것입니다 new String(anotherString)
생성자를. API에서 :
String(String original)
: 인수와 동일한 문자 시퀀스를 나타내도록 새로 작성된String
오브젝트를 초기화합니다 . 즉, 새로 만든 문자열은 인수 문자열의 복사본입니다. 원본의 명시적인 사본이 필요하지 않으면 문자열을 변경할 수 없으므로이 생성자를 사용할 필요가 없습니다.
다음 스 니펫을 검사하십시오.
String s1 = "foobar";
String s2 = "foobar";
System.out.println(s1 == s2); // true
s2 = new String("foobar");
System.out.println(s1 == s2); // false
System.out.println(s1.equals(s2)); // true
==
두 참조 유형은 참조 ID 비교입니다. equals
반드시 그런 것은 아닙니다 ==
. 일반적 ==
으로 참조 유형 에 사용 하는 것은 잘못입니다 . 대부분의 시간 equals
을 대신 사용해야합니다.
그럼에도 불구하고 어떤 이유로 든 문자열이 equals
아닌 두 개를 만들어야 하는 경우 생성자를 사용할 수 있습니다 . 그러나 이것은 매우 독특하며 거의 의도가 아니라는 것을 다시 말해야합니다.==
new String(anotherString)
"abc"
초가 있습니다. 그중 하나만 문자열 풀로 이동하고 다른 하나는이를 참조합니다. 그렇다면 s
적절한 새 객체가 될 것입니다.
문자열 리터럴 은 문자열 상수 풀로 이동 합니다.
아래의 스냅 샷은 시각적으로 이해 하여 오랫동안 기억할 수 있도록 도와줍니다 .
라인별로 객체 생성 :
String str1 = new String("java5");
생성자에서 문자열 리터럴 "java5"를 사용하면 새 문자열 값이 문자열 상수 풀에 저장됩니다. new 연산자를 사용하여 "java5"를 값으로 사용하여 힙에 새 문자열 오브젝트가 작성됩니다.
String str2 = "java5"
참조 "str2"는 문자열 상수 풀에 이미 저장된 값을 가리 킵니다.
String str3 = new String(str2);
"str2"에 의해 참조 된 것과 동일한 값을 가진 새로운 문자열 객체가 힙에 작성됩니다.
String str4 = "java5";
참조 "str4"는 문자열 상수 풀에 이미 저장된 값을 가리 킵니다.
총 오브젝트 : 힙-2, 풀-1
하나는 문자열 상수 풀에 문자열을 만듭니다.
String s = "text";
다른 하나는 상수 풀 ( "text"
)에 문자열을 만들고 일반 힙 공간 ( s
) 에 다른 문자열을 만듭니다 . 두 문자열의 값은 "text"와 동일합니다.
String s = new String("text");
s
나중에 사용하지 않으면 GC에 적합하지 않습니다.
반면에 문자열 리터럴은 재사용됩니다. "text"
클래스의 여러 곳에서 사용하는 경우 실제로는 하나의 문자열 (즉, 풀의 동일한 문자열에 대한 여러 참조)이됩니다.
이 개념을 JLS에서 "인터 닝"이라고합니다.
JLS 7 3.10.5의 관련 구절 :
또한 문자열 리터럴은 항상 String 클래스의 동일한 인스턴스를 나타냅니다. String.intern 메소드를 사용하여 고유 한 인스턴스를 공유하기 위해 문자열 리터럴 또는보다 일반적으로 상수 표현식 (§15.28)의 값인 문자열이 "인터 닝"되기 때문입니다.
예 3.10.5-1. 문자열 리터럴
편집 단위 (§7.3)로 구성된 프로그램 :
package testPackage; class Test { public static void main(String[] args) { String hello = "Hello", lo = "lo"; System.out.print((hello == "Hello") + " "); System.out.print((Other.hello == hello) + " "); System.out.print((other.Other.hello == hello) + " "); System.out.print((hello == ("Hel"+"lo")) + " "); System.out.print((hello == ("Hel"+lo)) + " "); System.out.println(hello == ("Hel"+lo).intern()); } } class Other { static String hello = "Hello"; }
그리고 컴파일 단위 :
package other; public class Other { public static String hello = "Hello"; }
출력을 생성합니다.
true true true true false true
문자열 리터럴은 String 클래스의 인스턴스에 대한 참조이며 클래스 또는 인터페이스의 이진 표현에서 CONSTANT_String_info 구조 (§4.4.3)에서 파생됩니다. CONSTANT_String_info 구조는 문자열 리터럴을 구성하는 일련의 유니 코드 코드 포인트를 제공합니다.
Java 프로그래밍 언어를 사용하려면 동일한 문자열 리터럴 (즉, 동일한 코드 포인트 시퀀스를 포함하는 리터럴)이 동일한 String 클래스 인스턴스 (JLS §3.10.5)를 참조해야합니다. 또한 String.intern 메소드가 문자열에서 호출되면 결과는 해당 문자열이 리터럴로 표시 될 때 리턴되는 동일한 클래스 인스턴스에 대한 참조입니다. 따라서 다음 표현식은 true 값을 가져야합니다.
("a" + "b" + "c").intern() == "abc"
문자열 리터럴을 도출하기 위해 Java Virtual Machine은 CONSTANT_String_info 구조에서 제공하는 코드 포인트 시퀀스를 검사합니다.
CONSTANT_String_info 구조에서 제공 한 것과 동일한 유니 코드 코드 포인트 시퀀스를 포함하는 String 클래스의 인스턴스에서 String.intern 메소드를 이전에 호출 한 경우 문자열 리터럴 파생 결과는 동일한 클래스의 클래스 인스턴스에 대한 참조입니다.
그렇지 않으면 CONSTANT_String_info 구조에 의해 제공된 일련의 유니 코드 코드 포인트를 포함하는 String 클래스의 새 인스턴스가 작성됩니다. 해당 클래스 인스턴스에 대한 참조는 문자열 리터럴 파생의 결과입니다. 마지막으로, 새로운 String 인스턴스의 인턴 메소드가 호출됩니다.
OpenJDK 7의 바이트 코드 구현을 살펴 보는 것도 유익합니다.
디 컴파일하면 :
public class StringPool {
public static void main(String[] args) {
String a = "abc";
String b = "abc";
String c = new String("abc");
System.out.println(a);
System.out.println(b);
System.out.println(a == c);
}
}
우리는 상수 수영장에 있습니다.
#2 = String #32 // abc
[...]
#32 = Utf8 abc
그리고 main
:
0: ldc #2 // String abc
2: astore_1
3: ldc #2 // String abc
5: astore_2
6: new #3 // class java/lang/String
9: dup
10: ldc #2 // String abc
12: invokespecial #4 // Method java/lang/String."<init>":(Ljava/lang/String;)V
15: astore_3
16: getstatic #5 // Field java/lang/System.out:Ljava/io/PrintStream;
19: aload_1
20: invokevirtual #6 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
23: getstatic #5 // Field java/lang/System.out:Ljava/io/PrintStream;
26: aload_2
27: invokevirtual #6 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
30: getstatic #5 // Field java/lang/System.out:Ljava/io/PrintStream;
33: aload_1
34: aload_3
35: if_acmpne 42
38: iconst_1
39: goto 43
42: iconst_0
43: invokevirtual #7 // Method java/io/PrintStream.println:(Z)V
참고 사항 :
0
및 3
: 동일한 ldc #2
상수로드 (리터럴)12
: 새로운 문자열 인스턴스가 생성됩니다 ( #2
인수로)35
: a
와 c
일반 객체로 비교if_acmpne
상수 문자열의 표현은 바이트 코드에서 매우 마술입니다.
new String
)위의 JVMS 인용문은 Utf8이 가리키는 경우마다 동일한 인스턴스가에 의해로드된다고 말합니다 ldc
.
필드에 대해 비슷한 테스트를 수행했으며
static final String s = "abc"
ConstantValue 속성을 통해 상수 테이블을 가리킴ldc
결론 : 문자열 풀에 대한 직접적인 바이트 코드 지원이 있으며 메모리 표현이 효율적입니다.
보너스 : 직접 바이트 코드를 지원하지 않는 Integer pool 과 비교하십시오 (예 : CONSTANT_String_info
아날로그 없음 ).
@Braj : 나는 당신이 다른 방법으로 언급했다고 생각합니다. 내가 틀렸다면 정정 해주세요
라인별로 객체 생성 :
문자열 str1 = 새 문자열 ( "java5")
Pool- "java5" (1 Object)
Heap - str1 => "java5" (1 Object)
문자열 str2 = "java5"
pool- str2 => "java5" (1 Object)
heap - str1 => "java5" (1 Object)
문자열 str3 = 새 문자열 (str2)
pool- str2 => "java5" (1 Object)
heap- str1 => "java5", str3 => "java5" (2 Objects)
문자열 str4 = "java5"
pool - str2 => str4 => "java5" (1 Object)
heap - str1 => "java5", str3 => "java5" (2 Objects)
str1
의 값에 포함되지 않는다 str2
거나 str3
또는 str4
어떤 식 으로든.
(의사) "bla"
와 같은 마법의 공장 이라고 생각하십시오 Strings.createString("bla")
. 팩토리는이 방법으로 생성 된 모든 문자열의 풀을 보유합니다.
호출되면 풀에이 값을 가진 문자열이 이미 있는지 확인합니다. true 인 경우이 문자열 객체를 반환하므로이 방법으로 얻은 문자열은 실제로 동일한 객체입니다.
그렇지 않은 경우 내부적으로 새 문자열 객체를 만들어 풀에 저장 한 다음 반환합니다. 따라서 다음에 동일한 문자열 값을 쿼리하면 동일한 인스턴스가 반환됩니다.
수동 생성 new String("")
은 문자열 리터럴 풀을 무시하여이 동작을 무시합니다. 따라서 equals()
객체 참조 등식 대신 문자 시퀀스를 비교하는 등호를 항상 확인해야합니다 .
차이점을 이해하는 간단한 방법은 다음과 같습니다.
String s ="abc";
String s1= "abc";
String s2=new String("abc");
if(s==s1){
System.out.println("s==s1 is true");
}else{
System.out.println("s==s1 is false");
}
if(s==s2){
System.out.println("s==s2 is true");
}else{
System.out.println("s==s2 is false");
}
출력은
s==s1 is true
s==s2 is false
따라서 new String ()은 항상 새 인스턴스를 만듭니다.
모든 문자열 리터럴은 문자열 리터럴 풀 내에 만들어지며 풀에서는 중복을 허용하지 않습니다. 따라서 둘 이상의 문자열 객체가 동일한 리터럴 값으로 초기화되면 모든 객체는 동일한 리터럴을 가리 킵니다.
String obj1 = "abc";
String obj2 = "abc";
"obj1"과 "obj2"는 동일한 문자열 리터럴을 가리키며 문자열 리터럴 풀에는 "abc"리터럴이 하나만 있습니다.
new 키워드를 사용하여 String 클래스 객체를 만들면 이렇게 생성 된 문자열이 힙 메모리에 저장됩니다. 그러나 String 클래스의 생성자에 매개 변수로 전달 된 모든 문자열 리터럴은 문자열 풀에 저장됩니다. new 연산자로 동일한 값을 사용하여 여러 개체를 만들면 매번 힙에 새 개체가 만들어 지므로이 새 연산자를 피해야합니다.
String obj1 = new String("abc");
String obj2 = new String("abc");
"obj1"과 "obj2"는 힙에있는 두 개의 다른 객체를 가리키며 문자열 리터럴 풀에는 "abc"리터럴이 하나만 있습니다.
또한 문자열의 동작과 관련하여 주목할 가치가있는 것은 문자열에 수행 된 새로운 할당 또는 연결이 메모리에 새로운 객체를 생성한다는 것입니다.
String str1 = "abc";
String str2 = "abc" + "def";
str1 = "xyz";
str2 = str1 + "ghi";
이제 위의 경우 :
Line 1 : "abc"리터럴이 문자열 풀에 저장됩니다.
2 행 : "abcdef"리터럴이 문자열 풀에 저장됩니다.
3 행 : 새로운 "xyz"리터럴이 문자열 풀에 저장되고 "str1"이이 리터럴을 가리 키기 시작합니다.
4 행 : 다른 변수에 값을 추가하여 값을 생성하므로 결과는 힙 메모리에 저장되고 "ghi"가 추가 된 리터럴은 문자열 풀에 존재하는지 확인하고 존재하지 않기 때문에 작성됩니다. 위의 경우.
String str = new String("hello")
문자열 상수 풀에 이미 문자열 "hello"가 포함되어 있는지 확인합니까? 있으면 문자열 상수 풀에 항목을 추가하지 않습니다. 존재하지 않으면 문자열 상수 풀에 항목이 추가됩니다.
힙 메모리 영역 str
에서 객체가 생성되고 힙 메모리 위치에서 생성 된 객체를 참조합니다.
str
String 상수 풀에 포함 된 포인트 객체에 대한 참조를 원한다면 명시 적으로 호출해야합니다.str.intern();
String str = "world";
문자열 상수 풀에 이미 문자열 "hello"가 포함되어 있는지 확인합니까? 있으면 문자열 상수 풀에 항목을 추가하지 않습니다. 존재하지 않으면 문자열 상수 풀에 항목이 추가됩니다.
위의 두 경우 모두 str
참조 "world"
는 상수 풀에 존재 하는 문자열을 가리 킵니다 .
문자열을 다음과 같이 저장할 때
String string1 = "Hello";
직접 JVM은 String 상수 풀이라는 별도의 메모리 블록 중에 지정된 가격으로 String 객체를 만듭니다.
그리고 우리가 다른 String을 시도하고 생성하려는 경향이있을 때마다
String string2 = "Hello";
JVM은 새로운 객체를 만들지 않고 기존 객체의 참조를 새로운 변수에 할당하는 대신 일정한 가격의 String 객체가 String 상수 풀 내에 존재하는지 여부를 확인합니다.
그리고 우리가 String을
String string = new String("Hello");
new 키워드를 사용하면 문자열 상수 풀의 내용에 관계없이 지정된 가격의 새로운 객체가 만들어집니다.