Java에서 문자열을 변경할 수없는 이유는 무엇입니까?


78

나는 그 이유를 이해할 수 없었다. 항상 다른 개발자와 마찬가지로 String 클래스를 사용하지만 그 값을 수정하면 새로운 String 인스턴스가 만들어집니다.

Java에서 String 클래스가 불변 인 이유는 무엇입니까?

StringBuffer 또는 StringBuilder와 같은 대안이 있다는 것을 알고 있습니다. 호기심 일뿐입니다.


20
기술적으로는 중복되지 않지만 Eric Lippert는 여기 에이 질문에 대한 훌륭한 대답을 제공합니다. programmers.stackexchange.com/a/190913/33843
Heinzi

답변:


105

동시성

Java는 동시성을 고려하여 처음부터 정의되었습니다. 종종 언급했듯이 공유 가변 변수는 문제가 있습니다. 스레드가 인식하지 않고 다른 스레드 뒤의 다른 것을 변경할 수 있습니다.

공유 문자열로 인해 여러 스레드 C ++ 버그가 발생했습니다. 한 모듈은 코드의 다른 모듈이 포인터를 저장하고 동일하게 유지할 때 변경하는 것이 안전하다고 생각했습니다.

이것에 대한 '해결책'은 모든 클래스가 전달되는 가변 객체의 방어 복사본을 만드는 것입니다. 변경 가능한 문자열의 경우 복사를 위해 O (n)입니다. 변경 불가능한 문자열의 경우 복사 할 수없는 동일한 객체 인 복사본이 아니기 때문에 복사본을 만드는 것은 O (1)입니다.

멀티 스레드 환경에서 변경 불가능한 객체는 항상 서로 안전하게 공유 할 수 있습니다. 이로 인해 메모리 사용량이 전반적으로 줄어들고 메모리 캐싱이 향상됩니다.

보안

많은 경우 문자열은 생성자에 대한 인수로 전달됩니다. 네트워크 연결과 프로토 타입은 가장 쉽게 생각할 수있는 두 가지입니다. 나중에 실행에서 결정되지 않은 시간에이를 변경할 수 있으면 보안 문제가 발생할 수 있습니다 (함수는 한 컴퓨터에 연결되었다고 생각했지만 다른 컴퓨터로 전환되었지만 개체의 모든 것이 첫 번째 컴퓨터에 연결된 것처럼 보입니다 ... 심지어 동일한 문자열).

Java는 리플렉션을 사용하도록 허용합니다. 이에 대한 매개 변수는 문자열입니다. 다른 방법으로 반영 될 수있는 문자열을 전달할 위험이 있습니다. 이것은 매우 나쁘다.

해시의 열쇠

해시 테이블은 가장 많이 사용되는 데이터 구조 중 하나입니다. 데이터 구조의 열쇠는 종종 문자열입니다. 변경할 수없는 문자열이 있다는 것은 해시 테이블이 매번 해시 키의 사본을 만들 필요가 없다는 것을 의미합니다. 문자열이 변경 가능하고 해시 테이블에서이를 만들지 않으면 거리에서 해시 키를 변경하는 것이 가능할 수 있습니다.

Java의 Object가 작동하는 방식은 모든 것이 해시 키를 가지고 있다는 것입니다 (hashCode () 메소드를 통해 액세스). 변경할 수없는 문자열이 있다는 것은 hashCode가 캐시 될 수 있음을 의미합니다. 문자열이 해시의 키로 사용되는 빈도를 고려하면 매번 해시 코드를 다시 계산할 필요없이 성능이 크게 향상됩니다.

부분 문자열

String을 변경할 수 없게하면 데이터 구조를 지원하는 기본 문자 배열도 변경할 수 없습니다. 이를 통해 substring메소드 에 대한 특정 최적화를 수행 할 수 있습니다 ( 필수 사항 은 아니며 일부 메모리 누수가 발생할 수도 있음).

당신이 할 경우 :

String foo = "smiles";
String bar = foo.substring(1,5);

bar은 '마일'입니다. 그러나 두 foo와는 bar단지 문자열 내에서 서로 다른 시작점과 끝점을 사용하여 - 더 문자 배열의 인스턴스를 감소하거나 복사, 같은 문자 배열로 백업 할 수 있습니다.

foo | | (0, 6)
    vv
    미소
     ^ ^
바 | | (1, 5)

이제 (메모리 누수) 단점은 1k 길이의 문자열이 있고 첫 번째와 두 번째 문자의 하위 문자열을 가져 가면 1k 긴 문자 배열에 의해 뒷받침된다는 것입니다. 이 배열은 전체 문자 배열의 값을 가진 원래 문자열이 가비지 수집 된 경우에도 메모리에 남아 있습니다.

JDK 6b14의 String에서 이것을 볼 수 있습니다 (다음 코드는 GPL v2 소스에서 가져온 것이며 예제로 사용됨)

   public String(char value[], int offset, int count) {
       if (offset < 0) {
           throw new StringIndexOutOfBoundsException(offset);
       }
       if (count < 0) {
           throw new StringIndexOutOfBoundsException(count);
       }
       // Note: offset or count might be near -1>>>1.
       if (offset > value.length - count) {
           throw new StringIndexOutOfBoundsException(offset + count);
       }
       this.offset = 0;
       this.count = count;
       this.value = Arrays.copyOfRange(value, offset, offset+count);
   }

   // Package private constructor which shares value array for speed.
   String(int offset, int count, char value[]) {
       this.value = value;
       this.offset = offset;
       this.count = count;
   }

   public String substring(int beginIndex, int endIndex) {
       if (beginIndex < 0) {
           throw new StringIndexOutOfBoundsException(beginIndex);
       }
       if (endIndex > count) {
           throw new StringIndexOutOfBoundsException(endIndex);
       }
       if (beginIndex > endIndex) {
           throw new StringIndexOutOfBoundsException(endIndex - beginIndex);
       }
       return ((beginIndex == 0) && (endIndex == count)) ? this :
           new String(offset + beginIndex, endIndex - beginIndex, value);
   }

하위 문자열이 어떻게 배열의 복사를 포함하지 않고 훨씬 더 빠른 패키지 수준의 문자열 생성자를 사용하는지에 유의하십시오 (큰 배열을 복제하지는 않지만 일부 큰 배열을 유지할 수 있음).

위의 코드는 Java 1.6 용입니다. 하위 문자열 생성자가 구현되는 방식은 Java 1.7.0_06에서 만든 Changes to String 내부 표현에 설명 된대로 Java 1.7에서 변경되었습니다 . 위에서 언급 한 메모리 누수 문제입니다. Java는 많은 문자열 조작을 가진 언어로 보이지 않았으므로 하위 문자열의 성능 향상은 좋은 일이었습니다. 이제는 수집되지 않은 문자열에 거대한 XML 문서가 저장되면 이것이 문제가되고 String하위 문자열과 동일한 기본 배열을 사용하지 않는 변경으로 인해 더 큰 문자 배열이 더 빨리 수집 될 수 있습니다.

스택을 남용하지 마십시오

하나는 문자열의 값 주위 대신 가변성 문제를 방지하기 불변의 문자열에 대한 참조를 전달합니다. 그러나 큰 문자열을 사용하면 이것을 스택에 전달하면 시스템에 악용됩니다 (전체 XML 문서를 스택에 문자열로 넣은 다음 빼거나 계속 전달합니다 ...).

중복 제거 가능성

물론, 이것은 왜 문자열이 불변이어야하는지에 대한 초기 동기가 아니었지만, 불변 문자열이 왜 좋은지에 대한 합리적인 이유를 살펴보면, 이것은 반드시 고려해야 할 사항입니다.

Strings를 조금 사용해 본 사람이라면 메모리를 빨아 들일 수 있다는 것을 알고 있습니다. 이것은 잠시 동안 튀어 나오는 데이터베이스에서 데이터를 가져 오는 것과 같은 일을 할 때 특히 그렇습니다. 이 찌르는 소리로 여러 번 같은 줄을 반복합니다 (각 행마다 한 번).

많은 대규모 Java 응용 프로그램이 현재 메모리에서 병목 현상이 발생합니다. 측정 결과에 따르면 이러한 유형의 응용 프로그램에 설정된 Java 힙 라이브 데이터의 약 25 %가 String 객체에 사용됩니다. 또한 해당 String 객체의 약 절반이 중복이며 여기서 중복은 string1.equals (string2)가 true임을 의미합니다. 힙에 중복 된 String 객체를 갖는 것은 본질적으로 메모리 낭비 일뿐입니다. ...

Java 8 업데이트 20에서는 이 문제를 해결하기 위해 JEP 192 (위에서 인용 한 동기 부여)가 구현되고 있습니다. 문자열 중복 제거의 작동 방식에 대한 세부 정보를 얻지 않고 문자열 자체를 변경할 수없는 것이 중요합니다. StringBuilders는 변경 될 수 있고 누군가가 당신 아래에서 무언가를 변경하기를 원하지 않기 때문에 중복 제거 할 수 없습니다. 변경 불가능한 문자열 (해당 문자열 풀과 관련됨)은 통과 할 수 있으며 동일한 두 문자열을 발견하면 한 문자열 참조를 다른 문자열 참조를 가리키고 가비지 수집기가 새로 사용되지 않은 문자열을 소비하게 할 수 있습니다.

다른 언어

(자바 선행) 목표 C는 가지고 NSStringNSMutableString.

C #과 .NET은 기본 문자열과 동일한 디자인 선택을 변경할 수 없게 만들었습니다.

루아 문자열도 변경할 수 없습니다.

파이썬 도.

역사적으로 Lisp, Scheme, Smalltalk는 모두 문자열을 인턴하여 불변으로 만듭니다. 보다 현대적인 동적 언어는 종종 문자열을 사용하여 변경할 수없는 방식을 사용합니다 ( String 이 아닐 수도 있지만 변경할 수는 없습니다).

결론

이러한 디자인 고려 사항은 여러 언어로 반복해서 작성되었습니다. 불변 문자열이 모든 어색함을 대체하는 것보다 우수하고 전체적으로 더 나은 코드 (버그가 적음)와 더 빠른 실행 파일로 이어진다는 것이 일반적인 합의입니다.


3
Java는 변경 가능하고 변경 불가능한 문자열을 제공합니다. 이 답변은 불변 문자열에서 수행 할 수있는 몇 가지 성능 이점과 불변 데이터를 선택할 수있는 몇 가지 이유를 자세히 설명합니다. 그러나 불변 버전이 기본 버전 인 이유에 대해서는 설명하지 않습니다.
Billy ONeal

3
@BillyONeal : 안전한 기본값과 안전하지 않은 대안 은 거의 항상 반대 방식보다 더 안전한 시스템으로 이어집니다.
Joachim Sauer

4
@BillyONeal 불변이 기본값이 아닌 경우 동시성, 보안 및 해시 문제가 더 일반적입니다. 언어 설계자들은 프로그래머의 효율성을 향상시키기 위해 여러 가지 일반적인 버그를 방지하기 위해 기본값이 설정된 언어를 만들기 위해 (C에 대한 응답으로) 언어를 선택했습니다 (이러한 버그에 대해 더 이상 걱정할 필요가 없습니다). 변경 불가능한 문자열보다 변경 불가능한 문자열의 버그 (명백하고 숨겨진)가 적습니다.

@Joachim : 나는 달리 주장하지 않습니다.
Billy ONeal

1
기술적으로 Common Lisp에는 "문자열과 같은"작업을 위해 변경 가능한 문자열과 변경 불가능한 식별자의 이름을 변경할 수없는 기호가 있습니다.
Vatine

21

내가 기억할 수있는 이유 :

  1. 문자열 풀의 경우, 문자열 풀의 경우 하나의 문자열 오브젝트 / 리터럴 (예 : "XYZ")이 많은 참조 변수에 의해 참조되므로 문자열 풀 기능을 전혀 사용할 수 없습니다. .

  2. 문자열은 많은 Java 클래스의 매개 변수로 널리 사용되어 왔습니다 (예 : 네트워크 연결 열기, 데이터베이스 연결 열기, 파일 열기). 문자열을 변경할 수없는 경우 심각한 보안 위협이 발생할 수 있습니다.

  3. 불변성은 String이 해시 코드를 캐시하도록 허용합니다.

  4. 스레드로부터 안전합니다.


7

1) 문자열 풀

Java 디자이너는 String이 모든 종류의 Java 응용 프로그램에서 가장 많이 사용되는 데이터 형식이 될 것임을 알고 있으며 처음부터 최적화하고 싶었습니다. 그 방향의 핵심 단계 중 하나는 문자열 리터럴을 문자열 풀에 저장하는 아이디어였습니다. 목표는 임시 문자열 객체를 공유하여 줄이는 것이었고 공유하려면 변경 불가능한 클래스 여야합니다. 서로 알 수없는 두 당사자와 변경 가능한 오브젝트를 공유 할 수 없습니다. 두 개의 참조 변수가 동일한 String 객체를 가리키는 가상의 예를 보자.

String s1 = "Java";
String s2 = "Java";

이제 s1이 객체를 "Java"에서 "C ++"로 변경하면 참조 변수에도 s2 = "C ++"값이 생겨서 알지 못합니다. String을 변경할 수 없게함으로써 String 리터럴을 공유 할 수있었습니다. 간단히 말해서, String pool의 핵심 아이디어는 Java에서 String final 또는 Immutable을 만들지 않으면 구현 될 수 없습니다.

2) 보안

Java는 모든 서비스 수준에서 안전한 환경을 제공한다는 점에서 분명한 목표를 가지고 있으며 String은 이러한 모든 보안 요소에서 중요합니다. 문자열은 많은 Java 클래스의 매개 변수로 널리 사용되었습니다. 예를 들어 네트워크 연결을 열기 위해 호스트 및 포트를 문자열로 전달할 수 있습니다. Java로 파일을 읽으려면 파일 및 디렉토리의 경로를 문자열로 전달하고 데이터베이스 연결을 열기 위해 데이터베이스 URL을 문자열로 전달하십시오. 문자열을 변경할 수없는 경우 사용자는 시스템의 특정 파일에 액세스 할 수있는 권한을 부여했을 수 있지만 인증 후 PATH를 다른 것으로 변경할 수 있으므로 심각한 보안 문제가 발생할 수 있습니다. 마찬가지로 네트워크의 데이터베이스 또는 다른 시스템에 연결하는 동안 문자열 값을 변경하면 보안 위협이 발생할 수 있습니다. 가변 문자열은 리플렉션에서도 보안 문제를 일으킬 수 있습니다.

3) 클래스 로딩 메커니즘에서 문자열 사용

String을 final 또는 Immutable로 만드는 또 다른 이유는 클래스 로딩 메커니즘에서 많이 사용 되었기 때문입니다. String은 변경할 수 없으므로 공격자는이 사실을 활용할 수 있으며 표준 Java 클래스 (예 : java.io.Reader)를로드하라는 요청을 악성 클래스 com.unknown.DataStolenReader로 변경할 수 있습니다. String을 최종적이고 불변으로 유지함으로써 최소한 JVM이 올바른 클래스를로드하고 있는지 확인할 수 있습니다.

4) 멀티 스레딩 장점

동시성 및 다중 스레딩은 Java의 주요 제품이므로 String 객체의 스레드 안전성에 대해 생각하는 것이 합리적이었습니다. String이 널리 사용될 것으로 예상되었으므로 변경 불가능하다는 것은 외부 동기화가 없음을 의미하며 여러 스레드간에 String을 공유하는 것과 관련하여 훨씬 깨끗한 코드를 의미합니다. 이 단일 기능은 이미 복잡하고 혼란스럽고 오류가 발생하기 쉬운 동시성 코딩을 훨씬 쉽게 만듭니다. String은 불변이고 스레드간에 공유하기 때문에 더 읽기 쉬운 코드가됩니다.

5) 최적화 및 성능

클래스를 불변으로 만들면,이 클래스는 일단 생성되면 변경되지 않을 것입니다. 이것은 캐싱과 같은 많은 성능 최적화를위한 개방 된 경로를 보장합니다. 문자열 자체는 변경하지 않을 것이므로 문자열은 해시 코드를 캐시합니다. 심지어 해시 코드를 느리게 계산하고 일단 생성하면 캐시하십시오. 간단한 세계에서 String 객체의 hashCode () 메서드를 처음 호출하면 해시 코드가 계산되고 hashCode ()에 대한 모든 후속 호출은 이미 계산 된 캐시 된 값을 반환합니다. String이 해시 기반 맵 (예 : Hashtable 및 HashMap)에 많이 사용되면 성능이 향상됩니다. 해시 코드 캐싱은 String 자체의 내용에 따라 달라지기 때문에 변경 불가능하고 최종적으로 만들 수 없었습니다.


5

Java Virtual Machine은 달리 수행 할 수없는 문자열 작업과 관련하여 여러 가지 최적화를 수행합니다. 예를 들어 값이 "Mississippi"인 문자열이 있고 "Mississippi".substring (0, 4)을 다른 문자열에 할당 한 경우 아는 한 "Miss"를 만들기 위해 처음 네 문자로 사본이 만들어졌습니다. . 모르는 것은 하나는 소유자 인 다른 하나는 "Mississippi"라는 원래 문자열을 공유하고 다른 하나는 위치 0에서 4까지의 해당 문자열 참조를 공유한다는 것입니다. 소유자에 대한 참조는 소유자가 소유자가 범위를 벗어날 때 가비지 수집기)

이것은 "Mississippi"만큼 작은 문자열에는 사소한 것이지만 더 큰 문자열과 여러 작업으로 문자열을 복사 할 필요가 없으므로 시간을 크게 절약 할 수 있습니다! 문자열을 변경할 수있는 경우 원본을 수정하면 하위 문자열 "복사본"에도 영향을 주므로이 작업을 수행 할 수 없습니다.

또한 Donal이 언급했듯이 이점은 단점으로 인해 크게 줄어들 것입니다. 라이브러리에 의존하는 프로그램을 작성하고 문자열을 리턴하는 함수를 사용한다고 가정하십시오. 그 가치가 일정하게 유지 될 것이라고 어떻게 확신 할 수 있습니까? 그러한 일이 발생하지 않도록하려면 항상 사본을 만들어야합니다.

동일한 문자열을 공유하는 두 개의 스레드가있는 경우 어떻게합니까? 다른 스레드가 현재 다시 쓰고있는 문자열을 읽고 싶지 않습니까? 따라서 문자열은 스레드로부터 안전해야하며, 이는 일반적인 클래스이므로 사실상 모든 Java 프로그램을 훨씬 느리게 만듭니다. 그렇지 않으면 해당 문자열이 필요한 모든 스레드에 대해 사본을 작성하거나 해당 문자열을 사용하여 코드를 동기화 블록에 넣어야합니다.

이러한 모든 이유로, 그것은 C ++과의 차별화를 위해 Java에 대한 초기 결정 중 하나였습니다.


이론적으로는 공유시 변경시 복사를 허용하는 다중 계층 버퍼 관리를 수행 할 수 있지만 다중 스레드 환경에서 효율적으로 작업하기는 매우 어렵습니다.
Donal Fellows

@DonalFellows 방금 Java Virtual Machine이 Java로 작성되지 않았기 때문에 공유 포인터 등을 사용하여 내부적으로 관리한다고 가정했습니다.
Neil

5

문자열의 불변성에 대한 이유는 언어의 다른 기본 유형과 일관성이 있기 때문입니다. 당신이이 경우 int값 (42)을 포함하고, 당신이 그것에 값 1을 추가, 당신은 42 당신은 시작 값으로 완전히 관련이없는 새 값 (43)을 얻을 변경하지 마십시오. 문자열 이외의 기본 요소를 변경하는 것은 개념적 의미가 없습니다. 그리고 문자열을 불변으로 취급하는 프로그램은 종종 추론하고 이해하기가 더 쉽습니다.

또한 Java는 실제로 볼 수 있듯이 가변 및 불변 문자열을 모두 제공합니다 StringBuilder. 실제로 기본값 은 불변 문자열입니다. StringBuilder어디에서나 참조를 전달 하려면 완벽하게 환영합니다. Java는 이러한 개념에 대해 별도의 유형 ( StringStringBuilder)을 사용 합니다. 유형 시스템에서 가변성을 표현하거나 지원할 수 없기 때문입니다. 타입 시스템에서 불변성을 지원하는 언어 (예 : C ++ const)에서는 종종 두 가지 목적을 모두 수행하는 단일 문자열 타입이 있습니다.

예, 문자열을 변경할 수 없으면 인턴과 같은 변경 불가능한 문자열에 특정한 최적화를 구현할 수 있으며 스레드간에 동기화하지 않고 문자열 참조를 전달할 수 있습니다. 그러나 이것은 간단하고 일관된 유형 시스템을 가진 언어의 목표와 메커니즘을 혼동합니다. 나는 이것을 모든 사람이 가비지 수집에 대해 잘못 생각하는 방식에 비유한다. 가비지 콜렉션은 "미사용 메모리의 교정"이 아닙니다. "무제한 메모리로 컴퓨터를 시뮬레이션" 입니다. 논의 된 성능 최적화는 불변 문자열의 목표가 실제 머신에서 잘 수행되도록하기위한 것입니다. 그러한 문자열이 처음에는 불변 인 이유가 아닙니다.


@ Billy-Oneal .. "42 값을 포함하는 int가 있고 그 값에 1을 더하면 42를 변경하지 않습니다. 새로운 값 43을 얻습니다.이 값은 시작과 완전히 관련이 없습니다. 값 " 확실합니까?
Shamit Verma

@Shamit : 예, 확실합니다. 1에서 42를 더하면 43이됩니다. 42라는 숫자가 43과 같은 의미는 아닙니다.
Billy ONeal

@Shamit : 마찬가지로, 당신은 같은 것을 할 수없고 43 = 6숫자 43이 숫자 6과 같은 것을 의미 할 것으로 기대합니다.
Billy ONeal

int i = 42; i = i + 1; 이 코드는 42를 메모리에 저장 한 다음 같은 위치의 값을 43으로 변경합니다. 실제로 변수 "i"는 43의 새로운 값을 얻습니다.
Shamit Verma

@Shamit :이 경우 i42가 아닌을 변경했습니다 string s = "Hello "; s += "World";. 고려하십시오 . variable 값을 변경했습니다 s. 그러나 문자열 "Hello ", "World""Hello World"변경할 수 있습니다.
Billy ONeal

4

불변성은 자신이 소유하지 않은 클래스가 보유한 상수는 수정할 수 없음을 의미합니다. 소유하지 않은 클래스에는 Java 구현의 핵심이되는 클래스가 포함되며, 수정해서는 안되는 문자열에는 보안 토큰, 서비스 주소 등이 포함됩니다. 실제로 이러한 종류를 수정할 수 없어야합니다. 사물함 모드에서 작동 할 때 이중으로 적용됩니다.

String이 변경 불가능한 경우, 문자열 내용이 발 밑에서 변경되지 않도록하는 컨텍스트에서 검색 할 때마다“사실 경우”사본을 가져와야합니다. 그것은 매우 비싸다.


4
이 정확히 동일한 인수는 뿐만 아니라 모든 유형에 적용됩니다 String. 그러나 예를 들어, Arrays는 변경 가능합니다. 그래서 왜 String불변이고 Array그렇지 않습니까 ? 그리고 불변성이 매우 중요하다면 왜 Java가 불변 객체를 생성하고 다루기가 어렵습니까?
Jörg W Mittag

1
@ JogWMittag : 기본적으로 그들이 얼마나 급진적이기를 원하는지에 대한 질문이라고 가정합니다. 불변의 String을 가지는 것은 Java 1.0 일로 상당히 급진적이었습니다. (주로 또는 독점적으로) 불변의 컬렉션 프레임 워크를 갖는 것은 언어를 광범위하게 사용하기에는 너무 급진적 일 수 있습니다.
Joachim Sauer

효과적인 불변 컬렉션 프레임 워크를 수행하는 것은 그러한 것을 쓴 사람 (Java는 아님)으로 말하면 성능을 발휘하기가 까다 롭습니다. 또한 나는 불변 적으로 배열이 있었으면 좋겠다. 그것은 저에게 많은 작업을 저장했을 것입니다.
Donal Fellows

@DonalFellows : pcollections 는 바로 그렇게하는 것을 목표로합니다 (단, 직접 사용하지는 마십시오).
Joachim Sauer

3
@ JörgWMittag : 모든 유형을 변경할 수 없다고 주장하는 사람들이 있습니다 (일반적으로 순수한 기능적 관점에서) . 마찬가지로, 병렬 및 동시 소프트웨어에서 변경 가능한 상태 작업을 다루는 모든 문제를 추가하면 변경 불가능한 객체를 사용하는 것이 변경 가능한 것보다 훨씬 쉽다 는 데 동의 할 수 있습니다.
Steven Evers

2

데이터를 받아들이고 정확성을 확인한 다음 전달하는 시스템을 상상해보십시오 (예 : DB에 저장).

데이터가 a String이고 5 자 이상이어야 한다고 가정합니다 . 방법은 다음과 같습니다.

public void handle(String input) {
  if (input.length() < 5) {
    throw new IllegalArgumentException();
  }
  storeInDatabase(input);
}

이제 우리는 storeInDatabase여기에서 부름 을받을 때 input요구 사항에 맞는다 는 데 동의 할 수 있습니다 . 그러나String 변경 가능 하다면 호출자input객체 가 확인 된 직후와 데이터베이스에 저장되기 전에 (다른 스레드에서) 객체를 변경할 수 있습니다 . 이것은 좋은 타이밍을 필요로 할 것이고 매번 잘되지 않을 수도 있지만 때로는 데이터베이스에 유효하지 않은 값을 저장할 수 있습니다.

변경 불가능한 데이터 유형은이 (및 많은 관련) 문제에 대한 매우 간단한 솔루션입니다. 어떤 값을 확인할 때마다 나중에 확인 된 조건이 여전히 사실이라는 사실에 의존 할 수 있습니다 .


설명 주셔서 감사합니다. 이처럼 handle 메소드를 호출하면 어떻게됩니까? handle (새 문자열 (입력 + "naberlan")). 나는 이와 같이 db에 유효하지 않은 값을 저장할 수 있다고 생각한다.
yfklon

1
@blank 다음부터 잘 inputhandle방법은 (관계없이 어떤 이미 너무 오래하지 원본 input 이다), 그것은 단순히 예외를 던질 것입니다. 메소드 호출 하기 전에 새 입력을 작성 중 입니다. 전혀 문제되지 않습니다.
Joachim Sauer

0

일반적으로 값 유형참조 유형 이 발생 합니다 . 값 유형을 사용하면 값을 나타내는 객체는 신경 쓰지 않고 값을 신경 쓰게됩니다. 내가 당신에게 가치를 주면 그 가치는 동일하게 유지 될 것으로 기대합니다. 갑자기 변경되는 것을 원하지 않습니다. 숫자 5는 값입니다. 갑자기 6으로 바뀌지 않을 것입니다. 문자열 "Hello"는 값입니다. 갑자기 "P *** off"로 변경 될 것으로 예상하지 않습니다.

참조 유형 을 사용하면 객체에 관심이 있으며 객체가 변경 될 것으로 예상합니다. 예를 들어, 종종 배열이 변경 될 것으로 예상합니다. 배열을 제공하고 그대로 유지하려면 배열을 변경하지 말라고 믿거 나 사본을 만드십시오.

Java 문자열 클래스를 사용하여 설계자는 결정을 내려야했습니다. 문자열이 값 유형처럼 동작하거나 참조 유형처럼 동작해야합니까? Java 문자열의 경우 값 유형이어야한다는 결정이 내려졌습니다. 즉, 오브젝트이므로 변경 불가능한 오브젝트 여야합니다.

반대의 결정이 내려졌지만 제 생각에는 많은 두통이 생겼을 것입니다. 다른 곳에서 말했듯이 많은 언어들이 같은 결정을 내렸고 같은 결론을 내 렸습니다. 하나의 문자열 클래스가있는 C ++는 예외이며 문자열은 상수이거나 일정하지 않을 수 있지만 C ++에서는 Java와 달리 개체 매개 변수가 참조가 아닌 값으로 전달 될 수 있습니다.


0

아무도 이것을 지적하지 않은 것이 정말 놀랍습니다.

답변 : 변경 가능하더라도 크게 도움이되지 않습니다. 추가 문제를 일으키는 것만 큼 유익하지는 않습니다. 가장 흔한 두 가지 돌연변이 사례를 살펴 보자.

문자열의 한 문자 변경

Java 문자열의 각 문자는 2 또는 4 바이트를 사용하므로 기존 사본을 변경하면 어떤 것이 있습니까?

2 바이트 문자를 4 바이트 1로 (또는 그 반대로) 대체하는 시나리오에서는 문자열의 나머지 부분을 왼쪽 또는 오른쪽으로 2 바이트 씩 이동해야합니다. 계산 관점에서 전체 문자열을 복사하는 것과 다르지 않습니다.

이것은 또한 일반적으로 원치 않는 불규칙 동작입니다. 누군가가 영어 텍스트로 응용 프로그램을 테스트하고 응용 프로그램이 중국과 같은 외국에 적용될 때 모든 것이 이상하게 시작된다고 상상해보십시오.

기존 문자열에 다른 문자열 (또는 문자) 추가

두 개의 임의의 문자열이있는 경우 두 개의 개별 메모리 위치에 있습니다. 첫 번째 문자열을 추가하여 첫 번째 문자열을 변경하려면 첫 번째 문자열의 끝에 추가 메모리를 요청할 수 없습니다. 아마도 이미 채워져 있기 때문입니다.

연결된 문자열을 완전히 새로운 위치로 복사해야합니다. 두 문자열을 모두 변경할 수없는 것과 동일합니다.

효율적으로 추가를 원한다면 StringBuilder, 미래의 가능한 추가를 위해 문자열 끝에 꽤 많은 양의 공간을 확보하는을 사용할 수 있습니다.


-2
  1. 그것들은 비싸고 불변으로 유지하면 기본 문자열의 바이트 배열을 공유하는 하위 문자열과 같은 것을 허용합니다. (새로운 바이트 배열을 만들고 복사 할 필요가 없으므로 속도 향상)

  2. 보안-패키지 또는 클래스 코드의 이름을 바꾸고 싶지 않습니다.

    [이전 3을 제거하면 StringBuilder src를 볼 수 있습니다-메모리가 문자열과 수정되지 않을 때까지 공유하지 않습니다 (1.3 또는 1.4에 있음)

  3. 캐시 해시 코드

  4. 가변 문자열의 경우 SB (빌더 또는 버퍼 필요에 따라)를 사용하십시오.


2
물론, 이런 일이 발생하면 줄의 더 큰 부분을 파괴 할 수 없다는 처벌이 있습니다. 인턴은 무료가 아닙니다. 많은 실제 프로그램의 성능을 향상시킵니다. 2. 해당 요구 사항을 충족시킬 수있는 "문자열"및 "ImmutableString"이 쉽게있을 수 있습니다. 3. 잘 모르겠습니다 ...
Billy ONeal

.삼. 해시 코드를 캐시해야합니다. 이것도 변경 가능한 문자열로 수행 할 수 있습니다. @ billy-oneal
tgkprog

-4

문자열은 Java에서 기본 데이터 유형이어야합니다. 만약 그렇다면 문자열은 기본적으로 변경 가능하고 최종 키워드는 변경 불가능한 문자열을 생성합니다. 가변 문자열은 유용하므로 stringbuffer, stringbuilder 및 charsequence 클래스에는 가변 문자열에 대한 여러 해킹이 있습니다.


3
이것은 이제 질문이 요구하는 것의 "왜"에 대한 답은 아닙니다. 또한 Java final은 그런 식으로 작동하지 않습니다. 가변 문자열은 해킹이 아니라 문자열의 가장 일반적인 용도와 jvm을 개선하기 위해 수행 할 수있는 최적화를 기반으로 실제 설계 고려 사항입니다.

1
"왜"에 대한 대답은 언어 설계 결정이 잘못되었습니다. 변경 가능한 문자열을 지원하는 약간 다른 세 가지 방법은 컴파일러 / JVM이 처리해야하는 해킹입니다.
CWallach

3
String과 StringBuffer는 원본이었습니다. StringBuilder는 나중에 StringBuffer의 디자인 어려움을 인식하여 추가되었습니다. 디자인이 여러 번 반복적으로 고려되고 매번 서로 다른 객체로 결정 되었기 때문에 다른 객체 인 가변 및 불변 문자열은 많은 언어에서 발견됩니다. C # "문자열을 변경할 수 없습니다".NET 문자열을 변경할 수없는 이유는 무엇입니까? , 목표 C NSString은 변경할 수 없지만 NSMutableString은 변경할 수 있습니다. stackoverflow.com/questions/9544182
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.