1. 문자열 문제
문자열은 실제로 일상적인 코딩 작업에 매우 유용하며 사용하기가 비교적 간단하므로 이에 대해 자세히 설명하는 사람은 거의 없습니다. 연구. 반면에 면접이나 필기 시험은 더 깊이 있고 어려운 질문이 포함되는 경우가 많습니다. 채용 시 후보자에게 가끔 관련 질문을 하는 것은 답변이 특히 정확하고 심층적이어야 한다는 의미는 아닙니다. 일반적으로 이러한 질문을 하는 목적은 첫 번째는 기본 지식에 대한 이해를 테스트하는 것입니다. 두 번째는 JAVA에 대한 기본 지식을 테스트하는 것입니다. 두 번째는 기술에 대한 지원자의 태도를 테스트하는 것입니다.
다음 프로그램이 어떤 결과를 출력하는지 볼까요? 모든 질문에 올바르게 대답하고 그 이유를 알 수 있다면 이 기사는 별로 의미가 없을 것입니다. 대답이 틀리거나 원리가 명확하지 않다면 다음 분석을 자세히 살펴보십시오. 이 기사는 각 프로그램의 결과와 결과를 출력하는 깊은 이유를 명확하게 이해하는 데 도움이 될 것입니다.
코드 세그먼트 1:
package com.paddx.test.string; public class StringTest { public static void main(String[] args) { String str1 = "string"; String str2 = new String("string"); String str3 = str2.intern(); System.out.println(str1==str2);//#1 System.out.println(str1==str3);//#2 } }
코드 세그먼트 2:
package com.paddx.test.string; public class StringTest01 { public static void main(String[] args) { String baseStr = "baseStr"; final String baseFinalStr = "baseStr"; String str1 = "baseStr01"; String str2 = "baseStr"+"01"; String str3 = baseStr + "01"; String str4 = baseFinalStr+"01"; String str5 = new String("baseStr01").intern(); System.out.println(str1 == str2);//#3 System.out.println(str1 == str3);//#4 System.out.println(str1 == str4);//#5 System.out.println(str1 == str5);//#6 } }
코드 세그먼트 3(1):
package com.paddx.test.string; public class InternTest { public static void main(String[] args) { String str2 = new String("str")+new String("01"); str2.intern(); String str1 = "str01"; System.out.println(str2==str1);//#7 } }
코드 세그먼트 3(2):
package com.paddx.test.string; public class InternTest01 { public static void main(String[] args) { String str1 = "str01"; String str2 = new String("str")+new String("01"); str2.intern(); System.out.println(str2 == str1);//#8 } }
설명의 편의를 위해 위 코드의 #1~#8 출력 결과를 인코딩하였으며, 아래 파란색 글씨 부분이 그 결과입니다.
2. 문자열 심층 분석
1. 코드 세그먼트 분석 1
문자열은 기본 유형에 속하지 않지만, 기본 유형과 같으며 리터럴을 통해 직접 값을 할당할 수 있습니다. 물론 new를 사용하여 문자열 객체 를 생성할 수도 있습니다. 그러나 리터럴 할당을 통해 문자열을 생성하는 것과 새 문자열을 생성하는 것에는 근본적인 차이가 있습니다.
리터럴 할당을 통해 문자열을 생성하는 경우 상수에 우선순위가 부여됩니다. 동일한 문자열이 풀에 이미 존재하는 경우 스택의 참조 가 해당 문자열을 직접 가리킵니다. 문자열이 없으면 상수 풀에 문자열이 생성됩니다. 스택의 참조는 이 문자열을 가리킵니다. new를 통해 문자열을 생성하면 힙에 문자열 객체가 직접 생성됩니다. (참고로 JDK 7 이후에는 HotSpot이 상수 풀을 영구 생성에서 힙으로 옮겼습니다. 자세한 내용은 "JDK8 메모리를 참조하세요." Model-The Disappearing PermGen" 기사), 스택의 참조는 개체를 가리킵니다. 힙에 있는 문자열 객체의 경우 intern() 메서드를 통해 상수 풀에 문자열을 추가하고 상수에 대한 참조를 반환할 수 있습니다.
이제 코드 세그먼트 1의 결과를 명확하게 이해할 수 있습니다.
결과 #1: str1이 문자열의 상수를 가리키므로 str2는 힙에서 생성된 개체입니다. 따라서 str1==str2는 false를 반환합니다.
결과 #2: str2는 str2의 값("string")을 상수 풀에 복사하는 intern 메서드를 호출하지만 해당 문자열은 이미 상수 풀에 존재합니다(즉, 문자열이 가리키는 문자열). by str1), 문자열에 대한 참조를 직접 반환하므로 str1==str2는 true를 반환합니다.
코드 세그먼트 1을 실행한 결과는 다음과 같습니다.
2. 코드 세그먼트 2 분석
두 번째 코드 세그먼트의 결과는 StringTest01.class 파일을 디컴파일하면 더 쉽게 이해할 수 있습니다.
상수 풀 콘텐츠(부분):
실행 지침( part) , 두 번째 열 #+ordinal은 상수 풀의 항목에 해당합니다):
위의 실행 과정을 설명하기 전에 먼저 두 가지 명령을 이해하세요.
ldc: 런타임 상수 풀에서 항목을 푸시하고, 지정된 항목의 참조를 상수 풀에서 스택으로 로드합니다.
astore_
이제 코드 세그먼트 2의 실행 프로세스를 설명하기 시작합니다.
0: ldc #2: 상수 풀의 두 번째 항목("baseStr")을 스택에 로드합니다.
2: astore_1 : 1의 참조를 첫 번째 지역 변수에 할당합니다. 즉, String baseStr = "baseStr"
3: ldc #2: 상수에서 두 번째 변수를 로드합니다. 풀 항목("baseStr")을 스택에 추가합니다.
5: astore_2: 3의 참조를 두 번째 지역 변수, 즉 final String에 할당합니다. baseFinalStr="baseStr"
6: ldc #3: 첫 번째 문자열을 로드합니다. 상수 풀 세 개의 항목("baseStr01")이 스택에 추가됩니다.
8: astore_3: 6의 참조를 세 번째 지역 변수, 즉 String str1="baseStr01";
에 할당합니다.9: ldc #3: 상수 풀의 세 번째 항목("baseStr01")을 스택에 로드합니다.
11: astore 4: 9의 참조를 네 번째 지역 변수에 할당합니다. String str2="baseStr01";
결과 #3: str1==str2는 둘 다 확실히 true를 반환합니다. str1과 str2는 상수 풀에서 동일한 참조 주소를 가리킵니다. 따라서 실제로 JAVA 1.6 이후에는 상수 문자열의 "+" 연산이 컴파일 단계에서 문자열로 직접 합성됩니다.
13: new #4: StringBuilder의 인스턴스를 생성합니다.
16: dup: 13에서 생성된 객체의 참조를 복사하여 스택에 푸시합니다.
17: Invokespecial #5: 상수 풀의 다섯 번째 항목인 StringBuilder.
위의 세 가지 지침은 StringBuilder 개체를 생성하는 데 사용됩니다.
20: aload_1 : 첫 번째 매개변수 값인 "baseStr"을 로드합니다.
21: Invokevirtual #6: StringBuilder 개체의 추가 메서드를 호출합니다.
24: ldc #7: 상수 풀의 일곱 번째 항목("01")을 스택에 로드합니다.
26: Invokevirtual #6: StringBuilder.append 메서드를 호출합니다.
29: Invokevirtual #8: StringBuilder.toString 메서드를 호출합니다.
32: astore 5: 29의 결과 참조 할당을 다섯 번째 지역 변수, 즉 변수 str3에 대한 할당으로 변경합니다.
결과 #4: str3은 실제로 stringBuilder.append()에 의해 생성된 결과이기 때문에 str1과 같지 않으며 결과는 false를 반환합니다.
34: ldc #3: 상수 풀의 세 번째 항목("baseStr01")을 스택에 로드합니다.
36: astore 6: 34의 참조를 여섯 번째 지역 변수, 즉 str4="baseStr01";
결과 #5: str1과 str4가 상수를 가리키기 때문에 세 번째 항목이 풀에 있으므로 str1==str4는 true를 반환합니다. 여기서도 final 필드의 경우 상수 교체가 컴파일 타임에 직접 수행되는 반면, non-final 필드의 경우 런타임에 할당 처리가 수행되는 현상도 확인할 수 있습니다.
38: new #9: 문자열 객체 생성
41: dup: 참조를 복사하여 스택에 푸시합니다.
42: ldc #3: 상수 풀의 세 번째 항목("baseStr01")을 스택에 로드합니다.
44: Invokespecial #10: String."
47: Invokevirtual #11: String.intern 메서드를 호출합니다.
38번부터 41번까지 해당 소스코드는 new String(“baseStr01″).intern()입니다.
50: astore 7: 47단계에서 반환된 결과를 변수 7에 할당합니다. 즉, str5는 상수 풀에서 baseStr01의 위치를 가리킵니다.
결과 #6: str5와 str1이 모두 상수 풀의 동일한 문자열을 가리키므로 str1==str5는 true를 반환합니다.
코드 세그먼트 2를 실행하면 출력 결과는 다음과 같습니다.
3. 코드 세그먼트 3 분석:
코드 세그먼트 3의 경우 JDK 1.6과 JDK 1.7에서 실행 결과가 다릅니다. 먼저 실행 결과를 살펴보고 그 이유를 설명하겠습니다.
JDK 1.6에서 실행 결과:
JDK 1.7에서 실행 결과:
코드 세그먼트 1의 분석에 따르면 str2와 str1은 원래 다른 위치를 가리키고 false를 반환해야 하기 때문에 JDK 1.6의 결과를 쉽게 얻을 수 있습니다.
이상한 문제는 JDK 1.7 이후에는 첫 번째 경우에는 true를 반환하지만 위치를 변경한 후에는 반환 결과가 false가 된다는 점입니다. 그 주된 이유는 JDK 1.7 이후 HotSpot이 상수 풀을 영구 생성에서 메타 공간으로 옮겼기 때문입니다. 이로 인해 JDK 1.7 이후의 인턴 방식은 구현에 있어 상대적으로 큰 변화를 겪었습니다. 로 이동하여 이 이미 상수 풀에 존재하는지 쿼리하고, 존재하는 경우 상수 풀에 참조를 반환합니다. 이는 이전과 다르지 않습니다. 상수 풀에서 찾을 수 없으면 문자열은 더 이상 상수 풀에 복사되지 않지만 원래 문자열에 대한 참조는 상수 풀에 생성됩니다. 그래서:
결과 #7: 첫 번째 경우에는 상수 풀에 문자열 "str01"이 없기 때문에 힙의 "str01"에 대한 참조가 상수 풀에 생성되고, 이를 수행할 때 리터럴 할당의 경우 상수 풀이 이미 존재하므로 참조를 직접 반환할 수 있습니다. 따라서 str1과 str2는 모두 힙의 문자열을 가리키고 true를 반환합니다.
결과 #8: 위치를 교환한 후 리터럴 할당(String str1 = "str01")을 수행할 때 상수 풀이 존재하지 않기 때문에 str1은 상수 풀의 위치를 가리키고 str2는 힙을 가리킵니다. intern 메소드는 의 객체에 사용되며 str1 및 str2에는 영향을 미치지 않으므로 false가 반환됩니다.
[관련 추천]
3. Java에서 intern 메소드의 개념은 무엇인가요
5. String 객체의 intern()에 대한 자세한 설명
위 내용은 Java의 intern() 메소드에 대한 심층 분석의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!