불변객체
"오늘의AI위키"의 AI를 통해 더욱 풍부하고 폭넓은 지식 경험을 누리세요.
1. 개요
불변 객체는 객체 지향 프로그래밍에서 객체의 상태가 변경되지 않도록 설계된 객체를 의미한다. 객체가 불변이면 객체 복제 시 참조만 복사하여 메모리 사용량을 줄이고, 멀티 스레드 환경에서 데이터 변경에 대한 걱정 없이 접근할 수 있어 스레드 안전성을 높인다. 다양한 프로그래밍 언어에서 불변 객체를 구현하기 위한 방법을 제공하며, 자바에서는 String, Integer와 같은 래퍼 클래스가 불변 객체로 제공된다. C++에서는 const 키워드를 사용하여 불변 객체를 만들 수 있으며, C#에서는 readonly 키워드와 불변 레코드를 통해 불변성을 지원한다. 파이썬은 일부 내장 자료형이 불변이며, 자바스크립트에서는 Object.freeze를 사용하여 객체를 불변으로 만들 수 있다.
더 읽어볼만한 페이지
불변객체 | |
---|---|
개요 | |
유형 | 값 할당 후 내부 상태를 변경할 수 없는 객체 |
특징 | 스레드 안전성 예측 가능성 캐싱 용이성 함수형 프로그래밍 지원 |
장점 | 코드의 안정성 향상 디버깅 용이 병렬 프로그래밍 환경에서 안전 |
단점 | 새로운 객체 생성으로 인한 성능 저하 가능성 메모리 사용량 증가 가능성 |
프로그래밍 언어별 지원 | |
자바 | `String` 클래스 `Integer`, `Double` 등의 래퍼 클래스 `java.time` 패키지의 클래스 `final` 키워드를 사용한 불변 클래스 생성 |
C# | `string` 형식 `struct`를 이용한 불변 타입 생성 `readonly` 키워드를 사용한 불변 필드 |
파이썬 | `tuple` 형식 `frozenset` 형식 |
스칼라 | `val` 키워드를 사용한 불변 변수 `case class`를 이용한 불변 클래스 생성 |
하스켈 | 모든 데이터 타입이 기본적으로 불변 |
구현 방법 | |
자바 | 모든 필드를 `final`로 선언 setter 메서드를 제공하지 않음 가변 객체에 대한 참조를 반환하지 않음 |
C# | 모든 필드를 `readonly`로 선언 setter 메서드를 제공하지 않음 가변 객체에 대한 참조를 반환하지 않음 |
활용 예시 | |
캐싱 | 불변 객체는 캐싱하기에 적합 |
동시성 프로그래밍 | 여러 스레드에서 안전하게 공유 가능 |
데이터 전송 객체 (DTO) | 데이터의 무결성을 보장 |
2. 개념
대부분의 객체 지향 프로그래밍 언어에서 객체는 참조 형태로 전달하고 받는다. 자바, C++, 파이썬, 루비 등이 그 예이다. 이 경우 객체가 참조를 통해 공유되면 그 상태가 변경될 가능성이 문제가 된다.
불변 객체는 객체 전체를 복제하는 대신 단순히 참조만 복제하면 되므로 메모리 절약 및 프로그램 실행 속도 향상에 유리하다. 참조는 일반적으로 객체 자체보다 훨씬 작기 때문이다(전형적으로 포인터 크기).
반면 가변 객체는 참조 복사 기술을 사용하기 어렵다. 가변 객체의 참조를 가진 사용자가 객체를 변경하면, 참조를 공유하는 모든 사용자가 그 영향을 받기 때문이다. 의도치 않은 동작을 막으려면 다른 참조 보유자에게 변경 사실을 알리고 대처해야 하는데, 이는 어려울 수 있다. 이 경우, 참조 대신 객체 전체를 defensive copy|방어적 복사영어하는 것이 일반적인 해결책이지만 비용이 든다.[17] 옵저버 패턴은 가변 객체 변경에 대처하는 다른 방법이다.
불변 객체는 멀티 스레드 프로그래밍에서도 유용하다. 데이터가 불변 객체로 표현되어 있으면 여러 스레드가 다른 스레드에 의해 데이터가 변경될 걱정 없이 데이터에 접근할 수 있다. 즉, 상호 배제가 필요 없다. 따라서 불변 객체가 가변 객체보다 스레드 안전하다고 볼 수 있다.
동일한 객체의 복사본 대신 항상 참조를 복제하는 기술은 인턴으로 알려져 있다. 인턴이 사용되고 있다면, 두 객체가 동일하다고 간주되는 것은 두 참조가 동일한 경우뿐이다. 일부 언어에서는 자동으로 인턴이 수행된다. 예를 들어, 파이썬은 문자열을 자동으로 인턴한다.
2. 1. 불변 변수
명령형 프로그래밍에서 내용이 변경되지 않는 프로그램 변수에 저장된 값은 실행 중에 변경될 수 있는 변수와 구별하기 위해 ''상수''라고 한다. 예로는 미터에서 피트로의 변환 계수 또는 원주율 값을 소수점 몇 자리까지 계산한 값 등이 있다.읽기 전용 필드는 프로그램 실행 시 계산될 수 있지만(미리 알려진 상수와 달리) 초기화된 후에는 변경되지 않는다.
2. 2. 약한 불변성 vs 강한 불변성
객체의 특정 필드만 불변인 경우 '약한 불변성'이라고 하며, 모든 필드가 불변인 경우 '강한 불변성'이라고 한다.[4] 이는 객체의 다른 부분은 변경 가능하더라도(''약한 불변성'') 객체 상태의 해당 부분을 변경할 방법이 없음을 의미한다. 일부 언어에서는 필드를 불변으로 지정하는 키워드(예: C++의 `const`, 자바의 `final`)를 사용한다. OCaml에서는 객체 또는 레코드의 필드가 기본적으로 불변이며, 변경 가능하도록 하려면 `mutable`로 명시적으로 표시해야 한다.2. 3. 객체 참조
대부분의 객체 지향 프로그래밍 언어에서 객체는 참조를 통해 전달되고 공유된다. 자바, C++, C#, 파이썬, 루비 등이 이러한 예시에 해당한다. 객체가 참조를 통해 공유되면 그 상태가 언제든지 변경될 수 있다는 점이 문제가 될 수 있다.[17]만약 객체가 불변 객체라면, 객체를 복제할 때 객체 전체를 복사하는 대신 단순히 참조만 복사하면 된다. 참조는 일반적으로 객체 자체보다 훨씬 작기 때문에(보통 포인터 크기) 메모리를 절약하고 프로그램 성능을 향상시킬 수 있다.
그러나 가변 객체의 경우 참조 복사 방식은 문제가 될 수 있다. 가변 객체의 참조를 가진 누군가가 객체를 변경하면, 그 참조를 공유하는 모든 곳에 영향을 미치기 때문이다. 만약 이것이 의도된 동작이 아니라면, 참조를 공유하는 다른 곳에 변경 사실을 알리고 적절히 대응하도록 하는 것은 어려울 수 있다. 이러한 경우, 객체 전체를 Defensive copy|방어적 복사영어하는 방법이 일반적인 해결책이지만, 비용이 많이 들 수 있다. 다른 대안으로는 옵저버 패턴을 사용하여 가변 객체의 변경에 대응할 수 있다.[17]
불변 객체는 멀티 스레드 프로그래밍에서도 유용하다. 데이터가 불변 객체에 저장되어 있으면, 여러 스레드가 동시에 데이터에 접근하더라도 다른 스레드에 의해 데이터가 변경될 위험이 없으므로 상호 배제가 필요하지 않다. 따라서 불변 객체는 가변 객체보다 스레드 안전하다고 할 수 있다.
객체 대신 참조를 복제하는 기술은 Intern (computer science)|인턴영어으로 알려져 있다. 인턴이 사용되면 두 객체가 같다고 판단되는 경우는 오직 두 참조가 동일한 경우뿐이다.
2. 4. 객체 참조 vs 복사
불변 객체는 객체를 복제할 때 객체 전체가 아닌 참조만 복사하므로 메모리가 절약되고 프로그램 성능에 좋다. 참조는 보통 객체 자체보다 훨씬 작기 때문이다. 반면 가변 객체는 참조 복사 방식으로 다루기 어렵다. 가변 객체의 참조를 가진 곳에서 객체를 변경하면 참조를 공유하는 모든 곳에 영향을 미치기 때문이다.의도치 않은 동작을 막으려면 참조를 가진 다른 곳에 변경 사실을 알리고 대처하는 추가 조치가 필요하다. 이 경우, 비용이 좀 들지만 객체 전체를 방어적 복사하는 방법이 있다. 또는 옵저버 패턴을 사용하여 가변 객체의 변경에 대처할 수 있다.
대부분의 객체 지향 언어에서 객체는 참조 형태로 주고받는다. 자바, C++, 펄, 파이썬, 루비 등이 그 예이다. 이 경우 객체가 참조를 통해 공유되면 그 상태가 변경될 가능성이 문제가 된다.
불변 객체는 객체 복제를 객체 전체가 아닌 단순히 참조를 복제하는 것으로 한정한다. 참조는 일반적으로 객체 자체보다 훨씬 작기 때문에(전형적으로 포인터 크기) 메모리를 절약하고 프로그램 실행 속도를 높인다.
가변 객체는 참조 복사 기술을 사용하는 것이 더 어렵다. 가변 객체의 참조를 보유한 자가 객체를 변경하면, 참조를 공유하는 모든 사람이 그 영향을 받는다. 이것이 의도한 작용이 아니라면, 다른 참조 보유자에게 제대로 대처하도록 통지하기 어렵다. 이 경우, 참조가 아닌 객체 전체의 방어적 복사[17]를 하는 것이 일반적이지만 비용이 든다. 옵저버 패턴은 가변 객체 변경에 대처하는 다른 방법이다.
등가 객체의 복사본 대신 항상 참조를 복제하는 기술은 인턴으로 알려져 있다. 인턴이 사용되고 있다면, 두 객체가 동일하다고 간주되는 것은 두 참조가 동일한 경우뿐이다. 일부 언어에서는 자동으로 인턴이 수행된다. 예를 들어, 파이썬은 문자열을 자동으로 인턴한다.
2. 5. 카피 온 라이트 (Copy-On-Write)
현대적인 하드웨어에서 지원하는 카피 온 라이트(Copy-On-Write)는 불변 객체의 장점을 활용하면서도 필요에 따라 효율적인 수정이 가능한 기법이다.카피 온 라이트의 동작 방식은 다음과 같다.
1. 초기 상태: 사용자가 객체 복제를 요청하면, 시스템은 실제 복사본을 만드는 대신 동일한 객체를 가리키는 새로운 참조(포인터)를 생성한다.
2. 변경 시도: 사용자가 이 참조를 통해 객체를 변경하려고 시도하면, 시스템은 그제서야 실제 복사본을 생성한다.
3. 변경 적용: 생성된 복사본에 변경 사항이 적용되고, 참조는 이 복사본을 가리키도록 업데이트된다.
4. 원본 유지: 다른 사용자들은 여전히 원본 객체를 참조하고 있으므로, 변경 사항에 영향을 받지 않는다.
이러한 방식 덕분에, 카피 온 라이트 환경에서는 모든 사용자가 마치 가변 객체를 사용하는 것처럼 보이지만, 실제로는 변경이 발생하기 전까지 불변 객체의 이점(공간 절약, 빠른 속도)을 누릴 수 있다.
카피 온 라이트는 가상 메모리 시스템에서 자주 사용되는데, 이는 프로그램들이 서로의 메모리를 침범할 걱정 없이 메모리를 절약할 수 있게 해주기 때문이다.[1]
2. 6. 인턴 (Intern)
동일한 객체의 복사본 대신 항상 참조를 사용하는 방식을 인턴이라고 한다. 인터닝을 사용하면 두 객체의 참조(일반적으로 포인터 또는 정수로 표시됨)가 동일한 경우에만 두 객체가 동일한 것으로 간주된다. 일부 언어는 이를 자동으로 수행한다. 예를 들어, 파이썬은 짧은 문자열을 자동으로 인턴한다. 인터닝을 구현하는 알고리즘이 가능한 모든 경우에 인터닝을 수행하도록 보장되면, 객체의 동일성을 비교하는 것은 해당 포인터를 비교하는 것으로 축소된다. 이는 대부분의 애플리케이션에서 속도가 크게 향상되는 것이다.[1]2. 7. 스레드 안전성 (Thread safety)
데이터가 불변 객체에 저장되어 있으면, 여러 스레드가 동시에 접근해도 특정 스레드의 데이터가 변경될 우려 없이 데이터에 접근할 수 있다. 즉, 상호 배제를 할 필요가 없다. 따라서 불변 객체는 가변 객체보다 스레드 안전하다고 간주된다.[17]2. 8. 불변성 위반
불변성은 컴파일 시간에 보장되는 개념이며, 메모리 상의 쓰기 불가능을 의미하는 것은 아니다. 프로그래머가 객체의 일반적인 인터페이스를 통해 무엇을 할 수 있는지를 나타내는 것이며, C 또는 C++(C++)에서 const 정확성을 위반하는 방식으로 절대적으로 할 수 있는 것은 아니다.[1]예를 들어 Java에서 String 클래스의 인스턴스는 불변 객체의 예시이다.
```java
String str = "ABC";
str.toLowerCase();
```
`toLowerCase()` 메서드는 변수 `str`의 값을 변경하지 않고, 새로운 String 객체를 생성하여 "abc" 값을 할당한다. `str`의 값을 "abc"로 변경하려면 다음과 같이 작성해야 한다.
```java
str = str.toLowerCase();
```
String 클래스는 인스턴스의 데이터를 변경하는 메서드를 제공하지 않는다.
객체가 불변하기 위해서는 외부에서 필드를 변경하거나 가변적인 필드에 접근하는 방법이 없어야 한다. 다음은 Java에서 가변 객체의 예시이다.
```java
class Cart
private final List
public Cart(List
public List
public int total() { /* return sum of the prices */ }
}
```
이 클래스의 인스턴스는 `getItems()`를 통해 `items` 필드에 접근하여 변경할 수 있으므로 불변 객체가 아니다. 부분적으로 불변성을 확보한 예시는 다음과 같다.
```java
class ImmutableCart
private final List
public ImmutableCart(List
this.items = new ArrayList
this.items.addAll(items);
}
public List
return Collections.unmodifiableList(items);
}
public int total() { /* return sum of the prices */ }
}
```
`Collections.unmodifiableList()`는 인수로 지정된 리스트의 읽기 전용 뷰를 반환하여 변경을 방지한다. 그러나 리스트 요소 자체의 불변성을 보장하지는 않는다.
FindBugs 같은 도구를 사용하여 클래스가 불변인지 확인할 수 있다.[1]
3. 배경
대부분의 객체 지향 프로그래밍 언어에서 객체는 참조 형태로 전달하고 받는다. 자바, C++, 펄, 파이썬, 루비 등이 그 예이다. 이 경우 객체가 참조를 통해 공유되면 그 상태가 변경될 가능성이 문제가 된다.
만약 객체가 불변 객체라면 객체를 복제할 때 객체 전체를 복제하는 것이 아니라 단순히 참조만 복제하면 된다. 참조는 일반적으로 객체 자체보다 훨씬 작기 때문에(전형적으로 포인터 크기) 메모리를 절약할 수 있으며 프로그램 실행 속도도 빨라진다.
등가 객체의 복사본을 만드는 대신 항상 참조를 복제하는 기술은 인턴으로 알려져 있다. 인턴이 사용되고 있다면, 두 객체가 동일하다고 간주되는 것은 두 참조가 동일한 경우뿐이다. 일부 언어에서는 자동으로 인턴이 수행된다. 예를 들어, 파이썬은 문자열을 자동으로 인턴한다.
3. 1. 객체 참조의 문제점
대부분의 객체 지향 프로그래밍 언어에서 객체는 참조 형태로 전달하고 받는다. Java, C++, 파이썬, 루비 등이 그 예이다. 객체가 참조를 통해 공유되면 그 상태가 언제든지 변경될 가능성이 커지므로 문제가 된다.[17]가변 객체의 참조를 가지고 있는 쪽에서 객체를 변경하면 참조를 공유하는 모든 곳에서 그 영향을 받는다. 이것이 의도한 동작이 아니라면, 참조를 가지고 있는 다른 쪽에 변경 사실을 알리고 대처하는 추가 대응이 필요하다. 이런 경우 비용은 조금 들지만 참조가 아닌 객체 전체를 방어적 복사(defensive copy)하는 간단한 방법으로 대응할 수 있다. 또는, 옵저버 패턴을 가변 객체의 변경에 대처하는 방법으로 사용할 수 있다.[17]
3. 2. 불변 객체의 이점
불변 객체는 객체를 복제할 때 객체 전체가 아닌 참조만 복사하므로 메모리를 절약하고 프로그램 성능을 향상시킨다. 참조는 일반적으로 객체 자체보다 훨씬 작기 때문이다.[17]가변 객체는 참조 복사 방식으로 다루기 어렵다. 가변 객체의 참조를 가진 사용자가 객체를 변경하면, 그 참조를 공유하는 모든 사용자가 영향을 받기 때문이다. 의도치 않은 변경을 막으려면 참조를 가진 다른 사용자에게 변경 사실을 알리고 대처하도록 해야 한다. 이 경우, 비용이 들지만 객체 전체를 방어적 복사하는 방법이나 옵저버 패턴을 사용할 수 있다.[17]
불변 객체는 멀티 스레드 프로그래밍에서 유용하다. 데이터가 불변 객체에 저장되면, 여러 스레드가 동시에 데이터에 접근해도 특정 스레드가 데이터를 변경할 우려가 없다. 즉, 상호 배제가 필요 없으므로 불변 객체는 가변 객체보다 스레드 안전하다고 볼 수 있다.
객체 전체 대신 참조를 복사하는 기법은 인턴으로 알려져 있다. 인턴이 사용되면 두 객체의 동일성 여부는 참조의 동일성 여부로 판단된다.
4. 구현
불변 객체는 객체가 생성된 후에는 상태를 변경할 수 없는 객체를 말한다. 하지만 이는 객체가 컴퓨터 메모리 내에서 쓰기가 불가능하다는 것을 의미하는 것은 아니다. 불변성은 컴파일 시점에 프로그래머가 무엇을 해야 하는지에 대한 문제이지, 무엇을 할 수 있는지에 대한 문제는 아니다.
현대 하드웨어는 가변 객체와 불변 객체의 장점을 결합한 카피 온 라이트(Copy-On-Write) 기술을 지원한다. 이 기술은 사용자가 객체 복제를 요청하면 실제 복사본을 만드는 대신 동일한 객체를 가리키는 참조를 생성한다. 사용자가 참조를 통해 객체를 변경하려고 하면, 그 때 실제 복사본을 만들고 해당 복사본을 가리키도록 참조를 변경한다. 이 방식은 다른 사용자가 원본 객체를 계속 참조하므로 영향을 주지 않으면서, 객체를 변경하지 않는 한 불변 객체의 효율성을 제공한다. 카피 온 라이트는 가상 메모리 시스템에서 자주 사용되며, 프로그램들이 서로의 메모리를 수정할 걱정 없이 메모리를 절약할 수 있게 해준다.
불변 객체의 전형적인 예는 Java의 `String` 클래스 인스턴스이다. Java에서 `String` 객체의 메서드는 인스턴스의 데이터를 변경하지 않는다. 예를 들어 `toLowerCase()` 메서드는 원래 문자열을 변경하는 대신 새로운 `String` 객체를 생성하여 반환한다.
객체가 불변성을 가지려면, 가변 필드의 존재 여부와 관계없이 외부에서 필드를 변경하거나 접근하는 방법이 없어야 한다. Java에서 가변 객체의 예시로는 `Cart` 클래스가 있다. 이 클래스는 `getItems()`를 통해 내부 필드에 접근하거나, 인스턴스화할 때 전달된 `List` 객체를 통해 필드를 변경할 수 있으므로 불변 객체가 아니다. `ImmutableCart` 클래스는 부분적으로 불변성을 확보한 예시이다. 그러나 이 역시 리스트 `items`의 요소가 불변이라는 보장은 없다. 완전한 불변성을 확보하려면 Decorator 패턴을 사용하여 리스트의 각 요소를 감싸는 방법을 고려할 수 있다.
C++에서는 `Cart` 클래스를 const-correct하게 구현하여 인스턴스를 불변(const) 또는 가변 객체로 생성할 수 있다. `const`로 선언된 객체는 멤버 함수도 `const`로 선언되어야 하며, 이를 통해 객체의 상태 변경을 방지한다.
Java에서 불변성을 완전히 확보하기 어려운 또 다른 이유는 클래스 상속 때문이다. 하위 클래스에서 `items`를 변경하는 setter 메서드를 구현할 수 있다. 이를 방지하기 위해 클래스와 메서드 인수에 `final` 수식자를 추가하고, "방어적 복사" 기법을 사용하여 외부 변경으로부터 내부 필드를 보호한다.
클래스가 불변인지 확인하는 도구로 FindBugs를 사용할 수 있다. FindBugs는 버그 유발 코드를 자동으로 감지하여 불변 클래스 작성에 도움을 준다. 자바스크립트에서 불변 객체를 다루려면 페이스북의 https://web.archive.org/web/20150809185757/http://facebook.github.io/immutable-js/를 사용할 수 있다.
4. 1. Java
javafinal class ImmutableCart
private final List
public ImmutableCart(final List
//방어적 복사한 뒤 리스트 요소의 실행 시 타입 검사를 한다.
//리스트의 요소를 final로 한다.
this.items = Collections.unmodifiableList(
Collections.checkedList(
(List
)
);
}
public List
// 내부 필드의 방어적 복사
return (List
}
public int total() { /* return sum of the prices */ }
}
```
`String`, `Integer` 등 기본 제공 래퍼 클래스들은 모두 불변이다. `final` 키워드는 불변 기본형과 객체 참조를 구현하는 데 사용되지만, 객체 자체를 불변으로 만들 수는 없다.
위 코드는 방어적 복사(Defensive Copy)를 통해 불변성을 보장한다. 생성자에서는 인수로 전달된 `List` 객체를 `clone()` 메서드로 복사하여 참조를 분리한다. `getItems()` 메서드에서도 `items` 필드를 직접 반환하지 않고 `clone()`을 통해 복사본을 반환하여 외부 변경으로부터 보호한다. `Collections.checkedList()`는 런타임에 타입 안정성을 보장하고, `Collections.unmodifiableList()`는 리스트 변경을 방지한다.
4. 2. C++
cppclass Cart {
public:
Cart(std::vector
const std::vector
int ComputeTotalCost() const {
if (total_cost_) {
return *total_cost_;
}
int total_cost = 0;
for (const auto& item : items_) {
total_cost += item.Cost();
}
total_cost_ = total_cost;
return total_cost;
}
private:
std::vector
mutable std::optional
};
```
C++는 `mutable` 키워드를 통해 추상적인 불변성을 제공하며, 이를 통해 멤버 변수를 `const` 메서드 내에서 변경할 수 있다.[4] 예를 들어 위의 `Cart` 클래스에서 `total_cost_` 멤버는 `ComputeTotalCost()` 메서드 내에서 값이 변경될 수 있지만 `ComputeTotalCost()` 메서드는 `const`로 선언되어, `Cart` 객체가 불변 객체처럼 동작하도록 설계할 수 있다.
```cpp
template
class Cart {
private:
std::shared_ptr
public:
explicit Cart(std::shared_ptr
std::shared_ptr
std::shared_ptr
int total() const { /* return sum of the prices */ }
};
```
C++에서는 참조 카운트 방식의 스마트 포인터 (`shared_ptr`)를 사용하여 Java처럼 생성자 호출 측과 상태를 공유하는 가변 객체 설계를 할 수도 있다. `shared_ptr`의 복사는 배열 전체 복사보다 비용이 훨씬 적기 때문에 의도적으로 가변 객체 설계를 선택하기도 한다.
4. 3. C#
C#에서는 `readonly` 키워드를 사용하여 클래스 필드의 불변성을 강제할 수 있다.[8] 모든 필드를 불변으로 강제함으로써 불변형을 얻을 수 있다.```csharp
class AnImmutableType
{
public readonly double _value;
public AnImmutableType(double x)
{
_value = x;
}
public AnImmutableType Square()
{
return new AnImmutableType(_value * _value);
}
}
```
C#에는 불변 레코드도 있다.[9][10]
```csharp
record Person(string FirstName, string LastName);
4. 4. Python
파이썬에서는 숫자, 부울, 문자열, 튜플, frozensets와 같은 일부 내장 자료형은 불변이지만, 사용자 정의 클래스는 일반적으로 가변이다. 클래스에서 불변성을 흉내내기 위해 속성 설정 및 삭제를 재정의하여 예외를 발생시킬 수 있다.[4]```python
class ImmutablePoint:
"""'x'와 'y'의 두 가지 속성을 가진 불변 클래스"""
__slots__ = ['x', 'y']
def __setattr__(self, *args):
raise TypeError("불변 인스턴스를 수정할 수 없습니다.")
__delattr__ = __setattr__
def __init__(self, x, y):
# 인스턴스 데이터를 저장하기 위해 self.value = value를 더 이상 사용할 수 없습니다.
# 따라서 상위 클래스를 명시적으로 호출해야 합니다.
super().__setattr__('x', x)
super().__setattr__('y', y)
```
표준 라이브러리 도우미인 `collections.namedtuple`과 `typing.NamedTuple`은 파이썬 3.6부터 사용할 수 있으며, 간단한 불변 클래스를 생성한다. 다음 예제는 위와 거의 동일하며, 튜플과 유사한 몇 가지 기능을 더한다.[4]
```python
from typing import NamedTuple
import collections
Point = collections.namedtuple('Point', ['x', 'y'])
# 다음은 위와 유사한 namedtuple을 생성합니다.
class Point(NamedTuple):
x: int
y: int
```
파이썬 3.7에 도입된 `dataclasses`를 사용하면 개발자는 고정 인스턴스를 통해 불변성을 흉내낼 수 있다. 고정된 dataclass가 생성되면 `dataclasses`는 호출 시 `FrozenInstanceError`를 발생시키도록 `__setattr__()`과 `__delattr__()`를 재정의한다.[4]
```python
from dataclasses import dataclass
@dataclass(frozen=True)
class Point:
x: int
y: int
4. 5. JavaScript
javascript// 원시 타입은 불변 객체이다.
function doSomething(x) { /* 여기서 x를 변경하는 것은 원본을 변경하지 않는다. */ };
var str = 'a string';
var obj = { an: 'object' };
doSomething(str); // 문자열, 숫자 및 부울 타입은 불변 객체이므로 함수는 복사본을 받는다.
doSomething(obj); // 객체는 참조로 전달되며 함수 내부에서 가변 객체이다.
doAnotherThing(str, obj); // `str`은 변경되지 않았지만 `obj`는 변경되었을 수 있다.
```
객체의 불변성을 확보하기 위해, 속성을 읽기 전용(`writable: false`)으로 정의할 수 있다.
```javascript
var obj = {};
Object.defineProperty(obj, 'foo', { value: 'bar', writable: false });
obj.foo = 'bar2'; // 무시됨
```
위의 방법은 새로운 속성을 추가하는 것을 막을 수는 없다. 기존 객체를 불변 객체로 만들기 위해 `Object.freeze`를 사용할 수 있다.
```javascript
var obj = { foo: 'bar' };
Object.freeze(obj);
obj.foo = 'bars'; // 속성을 편집할 수 없음, 무시됨
obj.foo2 = 'bar2'; // 속성을 추가할 수 없음, 무시됨
```
`const` 선언은 재할당할 수 없는 불변 참조를 생성한다. 그러나 `const` 선언을 사용한다고 해서 읽기 전용 참조의 값이 불변 객체라는 의미는 아니며, 단지 해당 이름이 새로운 값으로 할당될 수 없다는 의미이다.[15]
```javascript
const ALWAYS_IMMUTABLE = true;
try {
ALWAYS_IMMUTABLE = false;
} catch (err) {
console.log("불변 참조를 재할당할 수 없습니다.");
}
const arr = [1, 2, 3];
arr.push(4);
console.log(arr); // [1, 2, 3, 4]
```
불변 상태는 React의 도입 이후 자바스크립트에서 증가하는 추세이며, Redux와 같은 플럭스(Flux) 방식의 상태 관리를 선호한다.[15] 페이스북의 https://web.archive.org/web/20150809185757/http://facebook.github.io/immutable-js/를 이용하기도 한다.
4. 6. 기타 언어 (Ada, D, Perl, Racket, Rust, Scala)
Ada에서 모든 객체는 `constant` 키워드를 사용하여 ''변수'' (변경 가능, 일반적으로 암묵적인 기본값) 또는 `constant` (불변)으로 선언된다.[4]```ada
type Some_type is new Integer; -- 더 복잡할 수도 있습니다.
x: constant Some_type:= 1; -- 불변
y: Some_type; -- 변경 가능
```
서브프로그램 매개변수는 ''in'' 모드에서는 불변이고, ''in out'' 및 ''out'' 모드에서는 변경 가능하다.
```ada
procedure Do_it(a: in Integer; b: in out Integer; c: out Integer) is
begin
- - a는 불변입니다.
b:= b + a;
c:= a;
end Do_it;
```
D에는 변경할 수 없는 변수를 위한 두 개의 타입 한정자인 `const`와 `immutable`이 있다.[11] C++의 `const`, Java의 `final`, C#의 `readonly`와 달리 이러한 한정자는 전이적이며, 해당 변수의 참조를 통해 접근 가능한 모든 것에 재귀적으로 적용된다. `const`와 `immutable`의 차이점은 적용 대상에 있다. `const`는 변수의 속성이며, 참조된 값에 대한 가변 참조가 합법적으로 존재할 수 있어 값이 실제로 변경될 수 있다. 반대로, `immutable`은 참조된 값의 속성이며, 값과 그로부터 전이적으로 접근 가능한 모든 것은 변경될 수 없다(타입 시스템을 위반하지 않는 한, 이는 정의되지 않은 동작으로 이어진다). 해당 값에 대한 모든 참조는 `const` 또는 `immutable`로 표시되어야 한다. 기본적으로 자격이 없는 타입 `T`에 대해 `const(T)`는 `T`(가변)와 `immutable(T)`의 분리 집합이다.
```d
class C {
/*mutable*/ Object mField;
const Object cField;
immutable Object iField;
}
```
가변 `C` 객체의 경우, 해당 `mField`에 쓰기가 가능하다. `const(C)` 객체의 경우, `mField`는 수정할 수 없으며, `const`를 상속받는다. `iField`는 더 강력한 보장이므로 여전히 불변이다. `immutable(C)`의 경우 모든 필드는 불변이다.
다음과 같은 함수에서:
```d
void func(C m, const C c, immutable C i)
{ /* 중괄호 내부 */ }
```
중괄호 내부에서, `c`는 `m`과 동일한 객체를 참조할 수 있으므로, `m`에 대한 변경 사항은 간접적으로 `c`도 변경할 수 있다. 또한, `c`는 `i`와 동일한 객체를 참조할 수 있지만, 값이 불변이므로 변경 사항이 없다. 그러나 `m`과 `i`는 합법적으로 동일한 객체를 참조할 수 없다.
보장의 언어로 표현하자면, 가변은 보장이 없으며(함수가 객체를 변경할 수 있음), `const`는 함수가 아무것도 변경하지 않을 것이라는 외부 전용 보장이며, `immutable`은 양방향 보장이다(함수는 값을 변경하지 않으며 호출자도 값을 변경해서는 안 됨).
`const` 또는 `immutable`인 값은 선언 시점이나 생성자를 통해 직접 할당하여 초기화해야 한다.
`const` 매개변수는 값이 가변인지 여부를 잊어버리기 때문에, 유사한 구조인 `inout`은 어떤 의미에서 가변성 정보에 대한 변수 역할을 한다. `const(S) function(const(T))` 타입의 함수는 가변, const 및 immutable 인수에 대해 `const(S)` 타입 값을 반환한다. 반대로, `inout(S) function(inout(T))` 타입의 함수는 가변 `T` 인수에 대해 `S`를, `const(T)` 값에 대해 `const(S)`를, `immutable(T)` 값에 대해 `immutable(S)`를 반환한다.
immutable 값을 가변으로 캐스팅하는 것은 원래 값이 가변에서 나왔더라도 변경 시 정의되지 않은 동작을 발생시킨다. 가변 값을 immutable로 캐스팅하는 것은 그 후에 가변 참조가 남아 있지 않을 때 합법적일 수 있다. "표현식은 표현식이 고유하고, 전이적으로 참조하는 모든 표현식이 고유하거나 불변일 경우, 가변(...)에서 immutable로 변환될 수 있습니다."[11] 컴파일러가 고유성을 증명할 수 없는 경우, 캐스팅을 명시적으로 수행할 수 있으며, 프로그래머가 가변 참조가 존재하지 않도록 해야 한다.
`string` 타입은 `immutable(char)[]`, 즉, 불변 문자의 타입화된 메모리 슬라이스에 대한 별칭이다.[12] 부분 문자열을 만드는 것은 포인터와 길이 필드를 복사하고 수정하기만 하면 되며, 기본 데이터가 변경될 수 없으므로 안전하다. `const(char)[]` 타입의 객체는 문자열을 참조할 수 있지만 가변 버퍼도 참조할 수 있다.
const 또는 immutable 값의 얕은 복사본을 만들면 불변성의 외부 레이어가 제거된다. immutable 문자열(`immutable(char[])`)을 복사하면 문자열(`immutable(char)[]`)이 반환된다. 불변 포인터와 길이는 복사되고, 복사본은 가변이다. 참조된 데이터는 복사되지 않고, 예시에서 `immutable`과 같이 한정자를 유지한다. `dup` 함수를 사용하여 더 깊은 복사본을 만들어서 제거할 수 있다.
Perl에서는 Moo 라이브러리를 사용하여 모든 속성을 읽기 전용으로 선언함으로써 불변 클래스를 만들 수 있다.
```perl
package Immutable;
use Moo;
has value => (
is => 'ro', # 읽기 전용
default => 'data', # 생성자에 값을 제공하여 재정의할 수 있음: Immutable->new(value => 'something else');
);
1;
```
불변 클래스를 생성하려면 두 단계가 필요했다. 첫째, 객체 속성의 수정을 방지하는 접근자 (자동 또는 수동)를 생성하고, 둘째, 해당 클래스의 인스턴스에 대한 인스턴스 데이터의 직접적인 수정을 방지하는 것이다 (이것은 일반적으로 해시 레퍼런스에 저장되었으며 Hash::Util의 lock_hash 함수로 잠글 수 있었다).
```perl
package Immutable;
use strict;
use warnings;
use base qw(Class::Accessor);
# 읽기 전용 접근자 생성
__PACKAGE__->mk_ro_accessors(qw(value));
use Hash::Util 'lock_hash';
sub new {
my $class = shift;
return $class if ref($class);
die "Arguments to new must be key => value pairs\n"
unless (@_ % 2 == 0);
my %defaults = (
value => 'data',
);
my $obj = {
%defaults,
@_,
};
bless $obj, $class;
# 객체 데이터의 수정을 방지
lock_hash %$obj;
}
1;
```
또는 수동으로 작성된 접근자를 사용하여:
```perl
package Immutable;
use strict;
use warnings;
use Hash::Util 'lock_hash';
sub new {
my $class = shift;
return $class if ref($class);
die "Arguments to new must be key => value pairs\n"
unless (@_ % 2 == 0);
my %defaults = (
value => 'data',
);
my $obj = {
%defaults,
@_,
};
bless $obj, $class;
# 객체 데이터의 수정을 방지
lock_hash %$obj;
}
# 읽기 전용 접근자
sub value {
my $self = shift;
if (my $new_value = shift) {
# 새로운 값을 설정하려고 시도
die "This object cannot be modified\n";
} else {
return $self->{value}
}
}
1;
```
Racket은 핵심 쌍 유형("cons 셀")을 불변 객체로 만들어 다른 Scheme 구현과 크게 다르다. 대신 `mcons`, `mcar`, `set-mcar!` 등을 통해 병렬 가변 쌍 유형을 제공한다. 또한 불변 문자열과 벡터와 같은 많은 불변 유형이 지원되며, 이는 광범위하게 사용된다. 새로운 구조체는 필드가 명시적으로 가변으로 선언되지 않거나 전체 구조체가 가변으로 선언되지 않는 한 기본적으로 불변이다.
```racket
(struct foo1 (x y)) ; 모든 필드는 불변
(struct foo2 (x [y #:mutable])) ; 하나의 가변 필드
(struct foo3 (x y) #:mutable) ; 모든 필드는 가변
```
이 언어는 또한 기능적으로 구현된 불변 해시 테이블과 불변 딕셔너리도 지원한다.
Rust의 소유권 시스템을 통해 개발자는 불변 변수를 선언하고 불변 참조를 전달할 수 있다. 기본적으로 모든 변수와 참조는 불변이다. 가변 변수와 참조는 `mut` 키워드를 사용하여 명시적으로 생성된다.
Rust의 [https://doc.rust-lang.org/reference/items/constant-items.html 상수 항목]은 항상 불변이다.
```rust
// 상수 항목은 항상 불변입니다.
const ALWAYS_IMMUTABLE: bool = true;
struct Object {
x: usize,
y: usize,
}
fn main() {
// 명시적으로 가변 변수 선언
let mut mutable_obj = Object { x: 1, y: 2 };
mutable_obj.x = 3; // ok
let mutable_ref = &mut mutable_obj;
mutable_ref.x = 1; // ok
let immutable_ref = &mutable_obj;
immutable_ref.x = 3; // 오류 E0594
// 기본적으로 변수는 불변입니다.
let immutable_obj = Object { x: 4, y: 5 };
immutable_obj.x = 6; // 오류 E0596
let mutable_ref2 =
&mut immutable_obj; // 오류 E0596
let immutable_ref2 = &immutable_obj;
immutable_ref2.x = 6; // 오류 E0594
}
```
스칼라에서 모든 개체 (좁은 의미로는 바인딩)는 가변 또는 불변으로 정의될 수 있다. 선언 시, 불변 개체에는 `val` (값)을 사용하고, 가변 개체에는 `var` (변수)를 사용할 수 있다. 불변 바인딩은 재할당될 수 없지만, 가변 객체를 참조할 수 있으며 해당 객체에 대해 변경 메서드를 호출하는 것이 여전히 가능하므로, ''바인딩''은 불변이지만, 기본 ''객체''는 가변일 수 있다.
예를 들어, 다음 코드 조각을 살펴보자.
```scala
val maxValue = 100
var currentValue = 1
```
이 코드는 불변 개체 `maxValue` (정수 타입은 컴파일 시간에 추론됨)와 가변 개체 `currentValue`를 정의한다.
기본적으로 `List` 및 `Map`과 같은 컬렉션 클래스는 불변이므로, 업데이트 메서드는 기존 인스턴스를 변경하는 대신 새로운 인스턴스를 반환한다. 이는 비효율적으로 들릴 수 있지만, 이러한 클래스의 구현과 불변성에 대한 보장은 새 인스턴스가 기존 노드를 재사용할 수 있다는 것을 의미하며, 특히 복사본을 생성하는 경우 매우 효율적이다.[16]
5. 한국 개발 환경에의 적용
한국의 IT 환경은 세계적으로도 빠른 속도로 발전해 왔으며, 특히 더불어민주당 정부는 데이터 중심 경제 활성화와 디지털 뉴딜 정책 등을 통해 ICT 산업 발전에 박차를 가하고 있다. 이러한 환경에서 불변 객체의 활용은 더욱 중요해지고 있다.
(하위 섹션 '멀티스레드 환경'에서 상세히 다루므로 해당 내용은 생략)
5. 1. 멀티스레드 환경
멀티코어 프로세서가 보편화되고 서버 개발 환경이 변화하면서 멀티스레드 프로그래밍의 중요성이 커지고 있으며, 이에 따라 불변 객체의 활용이 더욱 강조된다. 여러 스레드는 다른 스레드에 의해 데이터가 변경될 염려 없이 불변 객체로 표현된 데이터에 대해 작동할 수 있다. 따라서 불변 객체는 가변 객체보다 더 스레드 안전하다고 간주된다.[1]참조
[1]
웹사이트
immutable adjective - Definition, pictures, pronunciation and usage notes - Oxford Advanced Learner's Dictionary at OxfordLearnersDictionaries.com
http://www.oxfordlea[...]
[2]
서적
Java Concurrency in Practice
Addison Wesley Professional
[3]
웹사이트
6.005 — Software Construction
https://web.mit.edu/[...]
[4]
웹사이트
Mutable and Immutable Objects: Make sure methods can't be overridden.
http://www.javaranch[...]
2003-04
[5]
서적
"Effective Java: Programming Language Guide"
Addison-Wesley
[6]
서적
"Effective Java: Programming Language Guide"
Addison-Wesley
[7]
웹사이트
Built-in Functions — Python v3.0 documentation
https://docs.python.[...]
[8]
서적
C# in Depth
Manning
2019-03-23
[9]
웹사이트
Use record types - C# tutorial - C#
https://learn.micros[...]
2023-11-14
[10]
웹사이트
Records - C# reference - C#
https://learn.micros[...]
2023-05-25
[11]
문서
D Language Specification § 18
https://dlang.org/sp[...]
[12]
문서
D Language Specification § 12.16
https://dlang.org/sp[...]
[13]
웹사이트
How to create Immutable Class and Object in Java – Tutorial Example
http://javarevisited[...]
Javarevisited.blogspot.co.uk
2013-03-04
[14]
웹사이트
Immutable objects
http://www.javapract[...]
javapractices.com
[15]
웹사이트
Immutability in JavaScript: A Contrarian View
http://desalasworks.[...]
[16]
웹사이트
Scala 2.8 Collections API – Concrete Immutable Collection Classes
http://www.scala-lan[...]
Scala-lang.org
[17]
문서
Java Practices→Defensive copying
http://www.javapract[...]
본 사이트는 AI가 위키백과와 뉴스 기사,정부 간행물,학술 논문등을 바탕으로 정보를 가공하여 제공하는 백과사전형 서비스입니다.
모든 문서는 AI에 의해 자동 생성되며, CC BY-SA 4.0 라이선스에 따라 이용할 수 있습니다.
하지만, 위키백과나 뉴스 기사 자체에 오류, 부정확한 정보, 또는 가짜 뉴스가 포함될 수 있으며, AI는 이러한 내용을 완벽하게 걸러내지 못할 수 있습니다.
따라서 제공되는 정보에 일부 오류나 편향이 있을 수 있으므로, 중요한 정보는 반드시 다른 출처를 통해 교차 검증하시기 바랍니다.
문의하기 : help@durumis.com