문제

Java 및 .NET(및 기타 일부 언어)에서 문자열을 불변으로 만들기로 결정한 이유는 무엇입니까?왜 변경 가능하게 만들지 않았나요?

도움이 되었습니까?

해결책

에 따르면 효과적인 자바, 4장, 73페이지, 2판:

"이에 대한 많은 타당한 이유가 있습니다.불변의 클래스는 변한 클래스보다 디자인, 구현 및 사용하기 쉽습니다.그들은 덜 오류가 발생하기 쉬우 며 더 안전합니다.

[...]

"불변 객체는 간단합니다. 불변의 대상은 정확히 하나의 상태, 그것이 만들어진 상태 일 수 있습니다.모든 생성자가 클래스 불변량을 확립하는지 확인하면 이러한 불변량이 귀하의 노력없이 항상 사실을 유지해야합니다.

[...]

불변 객체는 본질적으로 스레드로부터 안전합니다.동기화가 필요하지 않습니다. 동시에 접근하는 여러 스레드에 의해 손상 될 수 없습니다.이것은 스레드 안전성을 달성하기위한 가장 쉬운 방법입니다.실제로, 어떤 실이 불변의 물체에 대한 다른 스레드의 영향을 관찰 할 수 없습니다.그러므로, 불변 객체는 자유롭게 공유 가능

[...]

같은 장의 다른 작은 점들:

불변 객체를 공유할 수 있을 뿐만 아니라 그 내부도 공유할 수 있습니다.

[...]

불변 객체는 가변이든 불변이든 다른 객체를 위한 훌륭한 구성 요소가 됩니다.

[...]

불변 클래스의 유일한 단점은 각각의 고유한 값에 대해 별도의 객체가 필요하다는 것입니다.

다른 팁

적어도 두 가지 이유가 있습니다.

첫 번째 - 보안 http://www.javafaq.nu/java-article1060.html

String이 불변으로 만든 주된 이유는 보안이었습니다.이 예를보십시오 :로그인 확인이있는 파일 열린 메소드가 있습니다.통화가 OS로 전달되기 전에 필요한 인증을 처리하기 위해이 방법에 문자열을 전달합니다.String이 변이 할 수있는 경우 OS가 프로그램에서 요청을 받기 전에 인증 확인 후 컨텐츠를 수정할 수 있었을 때 파일을 요청할 수 있습니다.따라서 사용자 디렉토리에서 텍스트 파일을 열 권리가 있지만 어떻게 든 파일 이름을 변경할 때는 "Passwd"파일을 열도록 요청할 수 있습니다.그런 다음 파일을 수정할 수 있고 OS에 직접 로그인 할 수 있습니다.

두 번째 - 메모리 효율성 http://hikrish.blogspot.com/2006/07/why-string-class-is-immutable.html

JVM은 내부적으로 "문자열 풀"을 유지합니다.메모리 효율을 달성하기 위해 JVM은 문자열 객체를 풀에서 참조합니다.새 문자열 객체를 생성하지 않습니다.따라서 새 문자열 리터럴을 만들 때마다 JVM은 이미 존재하는지 여부에 관계없이 수영장에서 체크인합니다.이미 수영장에있는 경우 동일한 개체에 대한 참조를 제공하거나 수영장에서 새 개체를 만듭니다.많은 참조가 동일한 문자열 객체를 가리키며 누군가 값을 변경하면 모든 참조에 영향을 미칩니다.그래서 Sun은 그것을 불변으로 만들기로 결정했습니다.

실제로 Java에서 문자열을 변경할 수 없는 이유는 보안과 큰 관련이 없습니다.두 가지 주요 이유는 다음과 같습니다.

테드 안전:

문자열은 매우 널리 사용되는 객체 유형입니다.따라서 다중 스레드 환경에서 사용이 어느 정도 보장됩니다.스레드 간에 문자열을 공유하는 것이 안전한지 확인하기 위해 문자열은 변경할 수 없습니다.불변 문자열을 사용하면 스레드 A에서 다른 스레드 B로 문자열을 전달할 때 스레드 B가 예기치 않게 스레드 A의 문자열을 수정할 수 없습니다.

이는 이미 매우 복잡한 멀티스레드 프로그래밍 작업을 단순화하는 데 도움이 될 뿐만 아니라 멀티스레드 애플리케이션의 성능에도 도움이 됩니다.여러 스레드에서 액세스할 수 있는 경우 변경 가능한 개체에 대한 액세스를 어떻게든 동기화해야 합니다. 이를 통해 한 스레드가 다른 스레드에서 개체를 수정하는 동안 개체 값을 읽으려고 시도하지 않도록 해야 합니다.적절한 동기화는 프로그래머가 올바르게 수행하기 어렵고 런타임에 비용이 많이 듭니다.불변 객체는 수정할 수 없으므로 동기화가 필요하지 않습니다.

성능:

문자열 인터닝이 언급되었지만 이는 Java 프로그램의 메모리 효율성이 약간 향상되었음을 나타냅니다.문자열 리터럴만 인턴됩니다.즉, 동일한 문자열만 소스 코드 동일한 문자열 개체를 공유합니다.프로그램이 동일한 문자열을 동적으로 생성하는 경우 해당 문자열은 다른 개체로 표시됩니다.

더 중요한 것은 불변 문자열을 사용하면 내부 데이터를 공유할 수 있다는 것입니다.많은 문자열 작업의 경우 이는 기본 문자 배열을 복사할 필요가 없음을 의미합니다.예를 들어, String의 첫 5개 문자를 사용한다고 가정해 보겠습니다.Java에서는 myString.substring(0,5)을 호출합니다.이 경우 substring() 메서드가 수행하는 작업은 단순히 myString의 기본 char[]를 공유하는 새 String 객체를 생성하는 것입니다. 하지만 char[]의 인덱스 0에서 시작하고 인덱스 5에서 끝나는지 누가 알겠습니까?이를 그래픽 형식으로 나타내면 다음과 같이 됩니다.

 |               myString                  |
 v                                         v
"The quick brown fox jumps over the lazy dog"   <-- shared char[]
 ^   ^
 |   |  myString.substring(0,5)

이는 이러한 종류의 연산을 매우 저렴하게 만들고 연산이 원래 문자열의 길이나 추출해야 하는 하위 문자열의 길이에 의존하지 않기 때문에 O(1)입니다.많은 문자열이 기본 char[]를 공유할 수 있으므로 이 동작에는 메모리 이점도 있습니다.

스레드 안전성과 성능.문자열을 수정할 수 없는 경우 여러 스레드 간에 참조를 전달하는 것이 안전하고 빠릅니다.문자열이 변경 가능한 경우 항상 문자열의 모든 바이트를 새 인스턴스에 복사하거나 동기화를 제공해야 합니다.일반적인 애플리케이션은 문자열을 수정해야 할 때마다 문자열을 100번 읽습니다.위키피디아를 참조하세요 불변성.

"x가 왜 X를 돌려야합니까?"라고 물어야합니다. 이미 언급 한 혜택 때문에 불변성을 기본으로하는 것이 좋습니다. 플러프 공주.무언가가 변경 가능하다는 것은 예외여야 합니다.

불행하게도 현재 프로그래밍 언어의 대부분은 가변성을 기본값으로 설정하고 있지만 앞으로는 기본값이 불변성에 더 가깝기를 바랍니다(참조: 차세대 주류 프로그래밍 언어에 대한 희망 목록).

한 가지 요소는 문자열이 변경 가능한 경우 문자열을 저장하는 객체는 내부 데이터가 예고 없이 변경되지 않도록 주의해서 복사본을 저장해야 한다는 것입니다.문자열이 숫자와 같이 매우 원시적인 유형이라는 점을 감안할 때 참조로 전달되더라도 값으로 전달된 것처럼 처리할 수 있으면 좋습니다(이는 메모리 절약에도 도움이 됩니다).

우와!나는 여기서 잘못된 정보를 믿을 수 없습니다.불변의 문자열은 보안과 관련이 없습니다.누군가가 이미 실행 중인 애플리케이션의 개체에 액세스할 수 있는 경우(앱에서 문자열을 '해킹'하는 사람을 방지하려는 경우 가정해야 함) 확실히 해킹할 수 있는 다른 기회가 많이 있을 것입니다.

String의 불변성이 스레딩 문제를 해결한다는 것은 매우 참신한 아이디어입니다.흠 ...두 개의 서로 다른 스레드에 의해 변경되는 개체가 있습니다.이 문제를 어떻게 해결합니까?객체에 대한 액세스를 동기화하시겠습니까?으아아아아...누구도 객체를 전혀 변경하지 못하게 합시다. 그러면 지저분한 동시성 문제가 모두 해결될 것입니다!실제로 모든 객체를 불변으로 만든 다음 Java 언어에서 동기화된 구성을 제거할 수 있습니다.

(위의 다른 사람들이 지적한) 실제 이유는 메모리 최적화입니다.동일한 문자열 리터럴이 반복적으로 사용되는 것은 모든 애플리케이션에서 매우 일반적입니다.실제로 수십 년 전에는 많은 컴파일러가 문자열 리터럴의 단일 인스턴스만 저장하는 최적화를 수행했습니다.이 최적화의 단점은 문자열 리터럴을 수정하는 런타임 코드가 이를 공유하는 다른 모든 코드의 인스턴스를 수정하기 때문에 문제를 발생시킨다는 것입니다.예를 들어, 애플리케이션 어딘가의 함수가 문자열 리터럴 "dog"를 "cat"로 변경하는 것은 좋지 않습니다.printf("dog")를 사용하면 "cat"이 stdout에 기록됩니다.이러한 이유로 문자열 리터럴을 변경하려고 시도하는 코드(예:즉, 불변으로 만듭니다).일부 컴파일러(OS 지원)는 쓰기 시도가 있을 경우 메모리 오류를 일으키는 특수 읽기 전용 메모리 세그먼트에 문자열 리터럴을 배치하여 이를 수행합니다.

Java에서는 이를 인턴이라고 합니다.여기서 Java 컴파일러는 수십 년 동안 컴파일러가 수행한 표준 메모리 최적화를 따르고 있습니다.런타임에 이러한 문자열 리터럴이 수정되는 것과 동일한 문제를 해결하기 위해 Java는 단순히 String 클래스를 변경할 수 없도록 만듭니다(예:e, 문자열 내용을 변경할 수 있는 설정자가 없습니다).문자열 리터럴 인터닝이 발생하지 않은 경우 문자열을 변경할 수 없을 필요는 없습니다.

문자열은 기본 유형이 아니지만 일반적으로 값 의미 체계와 함께 사용하기를 원합니다.값처럼요.

가치는 뒤에서 변하지 않을 것이라고 믿을 수 있는 것입니다.당신이 쓰는 경우 : String str = someExpr();str을 사용하여 뭔가를 하지 않는 이상 변경되는 것을 원하지 않습니다.

객체로서의 문자열은 자연스럽게 포인터 의미를 가지므로 값 의미를 얻으려면 불변이어야 합니다.

이게 충격인 건 알지만...정말 불변인가요?다음을 고려하세요.

public static unsafe void MutableReplaceIndex(string s, char c, int i)
{
    fixed (char* ptr = s)
    {
        *((char*)(ptr + i)) = c;
    }
}

...

string s = "abc";
MutableReplaceIndex(s, '1', 0);
MutableReplaceIndex(s, '2', 1);
MutableReplaceIndex(s, '3', 2);
Console.WriteLine(s); // Prints 1 2 3

확장 방법으로 만들 수도 있습니다.

public static class Extensions
{
    public static unsafe void MutableReplaceIndex(this string s, char c, int i)
    {
        fixed (char* ptr = s)
        {
            *((char*)(ptr + i)) = c;
        }
    }
}

다음 작업을 수행합니다.

s.MutableReplaceIndex('1', 0);
s.MutableReplaceIndex('2', 1);
s.MutableReplaceIndex('3', 2);

결론:컴파일러에 의해 알려진 불변 상태에 있습니다.물론 위의 내용은 Java에 포인터가 없기 때문에 .NET 문자열에만 적용됩니다.그러나 C#에서는 포인터를 사용하여 문자열을 완전히 변경할 수 있습니다.포인터가 어떻게 사용되도록 의도되었는지, 실제 사용법이 있는지 또는 안전하게 사용되는지는 아닙니다.그러나 그것은 가능하므로 전체 "변경 가능" 규칙을 구부립니다.일반적으로 문자열의 인덱스를 직접 수정할 수 없으며 이것이 유일한 방법입니다.문자열의 포인터 인스턴스를 허용하지 않거나 문자열을 가리킬 때 복사본을 만들어 이를 방지할 수 있는 방법이 있지만 어느 쪽도 수행되지 않으므로 C#의 문자열을 완전히 변경할 수 없게 됩니다.

대부분의 경우 "문자열"은 의미 있는 문자열(사용/취급/생각/가정)입니다. 원자 단위, 숫자처럼.

따라서 문자열의 개별 문자가 변경 가능하지 않은 이유를 묻는 것은 정수의 개별 비트가 변경 가능하지 않은 이유를 묻는 것과 같습니다.

이유를 알아야합니다.생각 해보세요.

말하기 싫지만 안타깝게도 우리는 언어가 형편없어서 한 단어를 사용하려고 하기 때문에 이 문제에 대해 토론하고 있습니다. , 복잡하고 상황에 맞는 개념이나 객체 클래스를 설명합니다.

우리는 숫자를 다루는 방식과 유사하게 "문자열"을 사용하여 계산과 비교를 수행합니다.문자열(또는 정수)이 변경 가능한 경우 모든 종류의 계산을 안정적으로 수행하려면 해당 값을 변경할 수 없는 로컬 형식으로 고정하는 특수 코드를 작성해야 합니다.따라서 문자열을 숫자 식별자처럼 생각하는 것이 가장 좋지만 길이가 16, 32 또는 64비트가 아니라 수백 비트일 수도 있습니다.

누군가 "문자열"이라고 말하면 우리는 모두 다른 것을 생각합니다.특별한 목적을 염두에 두지 않고 단순히 문자 집합으로 생각하는 사람들은 물론 깜짝 놀라게 될 것입니다. 방금 결정했어 해당 캐릭터를 조작할 수 없어야 한다는 것입니다.그러나 "문자열" 클래스는 단순한 문자 배열이 아닙니다.그것은 STRING, 아니 char[].우리가 "문자열"이라고 부르는 개념에는 몇 가지 기본 가정이 있으며, 일반적으로 숫자와 같이 코딩된 데이터의 의미 있는 원자 단위로 설명할 수 있습니다.사람들이 "문자열 조작"에 대해 이야기할 때 실제로는 문자열 조작에 대해 이야기하는 것일 수도 있습니다. 문자 짓다 문자열, StringBuilder가 이에 적합합니다."문자열"이라는 단어가 실제로 무엇을 의미하는지 잠시 생각해 보십시오.

문자열이 변경 가능하다면 어떤 모습일지 잠시 생각해 보세요.다음 API 함수는 다음과 같은 경우 다른 사용자에 대한 정보를 반환하도록 속일 수 있습니다. 변하기 쉬운 사용자 이름 문자열은 이 함수가 사용하는 동안 다른 스레드에 의해 의도적으로 또는 의도하지 않게 수정되었습니다:

string GetPersonalInfo( string username, string password )
{
    string stored_password = DBQuery.GetPasswordFor( username );
    if (password == stored_password)
    {
        //another thread modifies the mutable 'username' string
        return DBQuery.GetPersonalInfoFor( username );
    }
}

보안은 단지 '접근 제어'에 관한 것이 아니라 '안전'과 '정확성 보장'에 관한 것이기도 합니다.메서드를 쉽게 작성할 수 없고 간단한 계산이나 비교를 안정적으로 수행하기 위해 의존할 수 없는 경우 해당 메서드를 호출하는 것은 안전하지 않지만 프로그래밍 언어 자체에 의문을 제기하는 것은 안전합니다.

불변성은 보안과 그다지 밀접하게 연관되어 있지 않습니다.이를 위해 적어도 .NET에서는 SecureString 클래스를 얻습니다.

그것은 절충안입니다.문자열은 문자열 풀로 이동하며 동일한 문자열을 여러 개 생성하면 동일한 메모리를 공유합니다.디자이너들은 프로그램이 동일한 문자열을 많이 사용하는 경향이 있기 때문에 이 메모리 절약 기술이 일반적인 경우에 잘 작동할 것이라고 생각했습니다.

단점은 연결로 인해 과도기적일 뿐이고 쓰레기가 되는 추가 문자열이 많이 만들어져 실제로 메모리 성능에 해를 끼친다는 것입니다.이러한 경우 메모리를 보존하는 데 사용할 수 있는 StringBuffer 및 StringBuilder(Java에서는 StringBuilder도 .NET에 있음)가 있습니다.

C++에서 문자열을 변경 가능하게 하기로 한 결정은 많은 문제를 야기합니다. Kelvin Henney가 작성한 훌륭한 기사를 참조하세요. 광우병.

COW = 쓰기 시 복사.

Java의 문자열은 실제로 불변이 아니며 리플렉션 및/또는 클래스 로딩을 사용하여 값을 변경할 수 있습니다.보안을 위해 해당 속성에 의존해서는 안 됩니다.예를 보려면 다음을 참조하세요. 자바의 마술

불변성이 좋습니다.효과적인 Java를 참조하세요.문자열을 전달할 때마다 복사해야 한다면 오류가 발생하기 쉬운 코드가 될 것입니다.또한 어떤 수정 사항이 어떤 참조에 영향을 미치는지에 대해 혼란을 겪습니다.Integer가 int처럼 동작하려면 불변이어야 하는 것과 마찬가지로, String은 기본 요소처럼 동작하려면 불변으로 동작해야 합니다.C++에서 값으로 문자열을 전달하는 것은 소스 코드에 명시적인 언급 없이 수행됩니다.

거의 모든 규칙에는 예외가 있습니다.

using System;
using System.Runtime.InteropServices;

namespace Guess
{
    class Program
    {
        static void Main(string[] args)
        {
            const string str = "ABC";

            Console.WriteLine(str);
            Console.WriteLine(str.GetHashCode());

            var handle = GCHandle.Alloc(str, GCHandleType.Pinned);

            try
            {
                Marshal.WriteInt16(handle.AddrOfPinnedObject(), 4, 'Z');

                Console.WriteLine(str);
                Console.WriteLine(str.GetHashCode());
            }
            finally
            {
                handle.Free();
            }
        }
    }
}

주로 보안상의 이유입니다.문자열이 변조되지 않는다는 것을 신뢰할 수 없다면 시스템을 보호하는 것이 훨씬 더 어렵습니다.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top