[java] Java 문자열이 변경 불가능합니까?

우리는 이것이 String자바에서 불변 이라는 것을 알고 있지만 다음 코드를 확인하십시오.

String s1 = "Hello World";
String s2 = "Hello World";
String s3 = s1.substring(6);
System.out.println(s1); // Hello World  
System.out.println(s2); // Hello World  
System.out.println(s3); // World  

Field field = String.class.getDeclaredField("value");
field.setAccessible(true);
char[] value = (char[])field.get(s1);
value[6] = 'J';
value[7] = 'a';
value[8] = 'v';
value[9] = 'a';
value[10] = '!';

System.out.println(s1); // Hello Java!  
System.out.println(s2); // Hello Java!  
System.out.println(s3); // World  

이 프로그램은 왜 이렇게 작동합니까? 그리고 왜 가치가 s1있고 s2변화 되었는가 s3?



답변

String 변경할 수 없지만 공개 API를 사용하여 변경할 수 없음을 의미합니다.

여기서하는 일은 리플렉션을 사용하여 일반 API를 우회하는 것입니다. 같은 방법으로 열거 형의 값을 변경하고 정수 자동 상자 등에 사용되는 조회 테이블을 변경할 수 있습니다.

이제 이유 s1s2변경 값은 둘 다 동일한 내부 문자열을 참조하기 때문입니다. 컴파일러는 이것을 수행합니다 (다른 답변에서 언급했듯이).

그 이유는 s3않습니다 하지 나는 그것이 공유 할 생각으로, 실제로 나에게 의외 조금했다 value배열 ( 은 자바의 이전 버전에서했던 자바 7u6 전에). 그러나의 소스 코드 를 보면 하위 문자열 Stringvalue문자 배열이 실제로 복사되어 Arrays.copyOfRange(..)있음을 알 수 있습니다. 이것이 변경되지 않는 이유입니다.

SecurityManager이러한 일을하는 악성 코드를 피하기 위해를 설치할 수 있습니다 . 그러나 일부 라이브러리는 이러한 종류의 리플렉션 트릭 (일반적으로 ORM 도구, AOP 라이브러리 등) 사용에 의존합니다.

*) 나는 처음에 Strings가 실제로 불변이 아니라 단지 “불변 불변”이라고 썼다. 현재 구현 String에서 value배열이 실제로 표시되어 있는 경우이 오류가 발생할 수 있습니다 private final. 그래도 Java에서 배열을 변경할 수없는 것으로 선언 할 수있는 방법이 없기 때문에 적절한 액세스 수정자를 사용해도 클래스 외부에 배열을 노출시키지 않도록주의해야합니다.


이 주제가 압도적으로 인기가있는 것처럼, 여기에 더 읽을 거리가 있습니다 : JavaZone 2009 의 Heinz Kabutz의 Reflection Madness 강연 은 OP의 많은 문제와 다른 반성 … 음 … 광기.

왜 이것이 유용한 지에 대해 다룹니다. 왜 대부분의 경우 피해야합니다. 🙂


답변

Java에서 두 문자열 기본 변수가 동일한 리터럴로 초기화되면 두 변수에 동일한 참조를 지정합니다.

String Test1="Hello World";
String Test2="Hello World";
System.out.println(test1==test2); // true

초기화

그것이 비교가 true를 반환하는 이유입니다. 세 번째 문자열은 substring()동일한 문자열을 가리키는 대신 새 문자열을 만드는 데 사용 됩니다.

하위 문자열

리플렉션을 사용하여 문자열에 액세스하면 실제 포인터가 나타납니다.

Field field = String.class.getDeclaredField("value");
field.setAccessible(true);

따라서 이것으로 변경하면 포인터를 보유하는 문자열이 변경되지만 s3새 문자열로 작성 substring()되므로 변경되지 않습니다.

변화


답변

String의 불변성을 피하기 위해 리플렉션을 사용하고 있습니다. 이것은 “공격”의 한 형태입니다.

이와 같이 만들 수있는 예제가 많이 있습니다 (예 : 객체를 인스턴스화 할 수도Void 있음). 문자열이 “불변”이 아님을 의미하지는 않습니다.

이 유형의 코드가 유리한 순간에 (GC 이전) 메모리에서 암호를 지우는 것과 같이 “유용한 코딩”이 될 수있는 사용 사례가 있습니다 .

보안 관리자에 따라 코드를 실행하지 못할 수 있습니다.


답변

리플렉션을 사용하여 문자열 객체의 “구현 정보”에 액세스하고 있습니다. 불변성은 객체의 공용 인터페이스 기능입니다.


답변

가시성 수정 자 및 최종 (즉, 불변성)은 Java의 악성 코드에 대한 측정이 아닙니다. 그것들은 단지 실수로부터 보호하고 코드를 유지 관리하기 쉽게 만드는 도구 일뿐입니다 (시스템의 큰 판매 포인트 중 하나). 따라서 백업 문자 배열과 같은 내부 구현 세부 정보에 액세스 할 수 있습니다.String 리플렉션을 통해 .

당신이 보는 두 번째 효과는 모든 String것이 변하는 동안 변화하는 것 s1입니다. Java String 리터럴의 특정 속성은 자동으로 인터 닝, 즉 캐시됩니다. 동일한 값을 가진 두 개의 문자열 리터럴은 실제로 동일한 객체입니다. 문자열을 만들면 new자동으로 구속되지 않으며이 효과가 나타나지 않습니다.

#substring최근까지 (Java 7u6) 비슷한 방식으로 작동하여 질문의 원래 버전에서 동작을 설명했습니다. 새로운 백업 문자 배열을 만들지 않았지만 원래 문자열의 배열을 재사용했습니다. 방금 오프셋과 길이를 사용하여 해당 배열의 일부만 나타내는 새로운 String 객체를 만들었습니다. 이것을 피하지 않으면 일반적으로 문자열을 변경할 수 없습니다. #substring또한 이 속성은 더 짧은 하위 문자열에서 생성 된 전체 원본 문자열을 가비지 수집 할 수 없음을 의미했습니다.

현재 Java 및 현재 버전의 질문에서 이상한 동작은 없습니다 #substring.


답변

문자열 불변성은 인터페이스 관점에서입니다. 리플렉션을 사용하여 인터페이스를 무시하고 String 인스턴스의 내부를 직접 수정합니다.

s1그리고 s2그들은 모두 동일한 “인턴”문자열 인스턴스에 할당되기 때문에 모두 변경됩니다. 이 기사 에서 문자열 평등과 인턴에 대한 부분에 대해 좀 더 자세히 알 수 있습니다 . 샘플 코드에서 다음을 s1 == s2반환 한다는 사실에 놀랄 수도 있습니다 true!


답변

어떤 버전의 Java를 사용하고 있습니까? Java 1.7.0_06부터 Oracle은 String, 특히 하위 문자열의 내부 표현을 변경했습니다.

오라클의 인용은 Java의 내부 문자열 표현을 조정합니다 .

새로운 패러다임에서 문자열 오프셋 및 개수 필드가 제거되었으므로 하위 문자열은 더 이상 기본 char [] 값을 공유하지 않습니다.

이 변경으로 인해 반사 (???)없이 발생할 수 있습니다.