제 홈페이지의 모든 글은 anti-nhn license에 따릅니다.



Immutable에 대해서..

Immutable이란..

Immutable이란 생성후 변경 불가한 객체입니다. 그래서 immutable에는 set 메쏘드가 없습니다. 멤버 변수를 변경할 수 없습니다. return type이 void인 메쏘드도 없습니다. 주로 void 메쏘드는 뭔가를 하고(하지 않을 수도 있고.. ) 멤버변수를 변경하는 역할을 하는 것이기 때문에 쓸 일이 거의 없습니다. (물론, 콘솔에 뭔가를 찍는 것과 같은 예외적인 void는 있을 수 있습니다.)
Immutable을 쓰면, 멀티 쓰레드 환경에서 좀 더 신뢰할 수 있는 코드를 만들어 내기가 쉽습니다. 멀티 쓰레드 프로그램을 짜보셨다면 아시겠지만, 멀테 쓰레드 환경에서는 에러보다 비정상적 작동의 경우가 많습니다. 에러도 아니기 때문에 찾아내기도 어렵습니다. 게다가 항상 생기는 것도 아니고 백번에 한번 천번에 한번 식으로 문제가 생겨 정말 머리 아픈 경우가 한 두번이 아닙니다.
Immutable을 쓰게 되면, 이런 요소들을 많이 줄일 수 있습니다.

대표적인 Immutable 클래스

String, Boolean, Integer, Float, Long 등등이 있습니다. 여기서 주의할 점은 변경불가라는 것은 heap 영역에서의 변경불가라는 뜻입니다. String a="a"; a="b"; 와 같이 재할당은 가능합니다. 이는 a가 reference하고 있는 heap 영역의 객체가 바뀌는 것이지 heap영역에 있는 값이 바뀌는 것이 아닙니다.

String vs StringBuffer

String과 StringBuffer에는 비슷한 메쏘드들이 많이 있어서 비슷해 보이지만, 결정적인 차이가 있습니다. String은 Immutable 입니다. StringBuffer는 아닙니다. StringBuffer가 String에 비해서 훨씬 빠르다는 얘기를 들어보셨나요? 그건 객체를 새로 생성할 필요가 없기 때문입니다.

String에는 없고 StringBuffer에만 있는 대표적인 메쏘드는 append, delete 등일 겁니다. 멤버 변수를 변화시켜 값을 바꿀 수 있는 거죠. 그런데 잘 보며 이들의 리턴은 StringBuffer 타입입니다. 어차피 얘네도 객체를 새로 만들어낸다면, String과 별 차이가 없어보입니다. 그러나 객체를 새로 만들어 내는 것이 아닙니다. 
아래 코드를 실행시켜보세요.
StringBuffer b = new StringBuffer();
StringBuffer a = b.append("test");
System.out.println(a == b);
true가 나옵니다. 객체를 새로 만드는 것이 아니라 return this 로 되어있습니다. 굳이 리턴을 하는 이유는 아래와 같은 코딩을 가능하게 해주려는 것 같습니다.

StringBuffer test = new StringBuffer();
test.append("a").append("b").append("c").append("d");

와 같은 식으로 여러줄 코딩을 한 줄로 할 수 있게 해주려는 것 같습니다.. 아님 말구요.-_-;

Immutable의 유용성과 위험성

멀티쓰레드 환경에서 하나의 객체에 접근을 하는데 각각의 쓰레드끼리는 영향을 받으면 안 되는 경우가 있습니다. 그럴 때 한 번 만들어진 객체의 값이 변하지 않는다는 게 보장이 되면 편하겠죠.

String a  = "";
while(어떤 조건문){
    a += "머시기";
    if(딴 조건문){
        break;
    }
}

위와 같은 코드는 쓰면 안 됩니다. a+= "머시기" 구문이 문젭니다. 객체를 계속 생성해 냅니다. Immutable은 값이 계속 변경될 수 있는 곳에 쓰면 메모리 왕창 잡아먹습니다. 값이 완전히 정리된 후에 한 큐에 immutable로 만들어 내셔야 합니다. (물론, 가비지 컬레션이 돌면서 정리를 하기 때문에 치명적으로 위험한 수준의 행동은 아닙니다.)

java.util.Collections 에 unmodifiable머시기 하는 메쏘드들이 있습니다. 얘네들을 이용하면, Set, List, Map 등을 immutable로 사용할 수 있습니다. 다만, add, put과 같은 메쏘드를 호출할 경우에는 UnsupportedOperationException 가 발생합니다. 예외 상황을 고려해야 합니다.

Immutable은 보통 final 클래스로 정의합니다.

처음에 자바를 할 때 String이란 객체에 ltrim이란 메쏘드를 추가하고 싶었습니다. (왼쪽만 trim하는 메쏘듭니다.) 상속을 받아 새로운 클래스를 만들어 해결하려고 했습니다. 헛, 그런데 final로 정의가 되어있어서 상속을 받을 수가 없더군요.
final로 정의가 되지 않으면, 상속을 받은 클래스가 Immutable을 깨버릴 수가 있기 때문입니다.

잘못된 Immutable의 구현

package immutable;

import java.text.SimpleDateFormat;
import java.util.Date;

public final class WrongImmutable {
    private final Date date;
    private final SimpleDateFormat dateFormat;
    public WrongImmutable(Date date){
        this.date = date;
        dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    }
    public String getMessage(){
        return dateFormat.format(date);
    }
}

위의 소스를 보세요. date라는 변수가 final로 정의 되어있으며, setter가 없어 오직 생성자에서만 값을 지정할 수 있는 것 같아보입니다. 그러나 아래와 같은 테스트 클래스를 실행시켜보시면, 위의 코드가 잘못되었음을 알 수 있습니다.

package immutable;

import java.util.Date;

public class WrongImmutableTest {
    public static void main(String[] args) {
        Date testDate = new Date();
        WrongImmutable wrongImmutable = new WrongImmutable(testDate);
        testDate.setTime(testDate.getTime() + 10000000);
        System.out.println(wrongImmutable.getMessage());
    }
}

WrongImmutable의 생성자에 인자로 넣은 Date를 외부에서 값을 변경시키면 WrongImmutable의 멤버 변수의 값이 변경이 되고 맙니다. Immutable에서는 멤버 변수가 외부로 공개되어 변경이 가능하면 안 됩니다.
그럼 처음에 원했던 대로 정상적인 Immutable이 되도록 하려면 인자로 받은 Date 객체를 그대로 사용하는 것이 아니라, 어떤 식으로든 복사를 해서 써야 합니다. 생성자에서 멤버 변수의 값을 할당하는 부분을

this.date = new Date(date.getTime());
와 같이 바꿔야 합니다.

자바에 진짜 Immutable이란 건 없다!

java의 reflection을 이용하면 변경 가능합니다. 다음 코드를 실행시켜 보세요.

import java.lang.reflect.Field;

public class EditUsingReflection {
    public static void main(String[] args) throws IllegalArgumentException, SecurityException, IllegalAccessException, NoSuchFieldException {
        String s = "string!";
        edit(s);
        System.out.println(s);
    }
    public static void edit(String s) throws IllegalArgumentException, IllegalAccessException, SecurityException, NoSuchFieldException {
        Field stringValue = String.class.getDeclaredField("value");
        stringValue.setAccessible(true);
        stringValue.set(s, s.toUpperCase().toCharArray());
    }
}

String 객체는 내부적으로 value라는 이름의 char[]로 그 값을 관리합니다. 물론 private으로 선언되어있지요.
reflection을 이용하면 멤버변수에 걸려있는 private을 무시하고 접근해 버릴 수가 있습니다. 결국 reflection을 이용하면 모든 멤버변수에 대한 수정이 가능합니다. Immutable이란 건 reflection을 제외시켜 놓고 생각할 때만 가능합니다.

by 삼실청년 | 2007/09/28 11:35 | 컴터질~ | 트랙백 | 핑백(2) | 덧글(6)

트랙백 주소 : http://iilii.egloos.com/tb/3809685
☞ 내 이글루에 이 글과 관련된 글 쓰기 (트랙백 보내기) [도움말]
Linked at 건실성실착실 3실 청년! : .. at 2008/06/16 22:56

... b의 "하하"는 a에서 등록된 풀에서 가져옵니다. 결국 같은 인스턴스가 되는 것입니다.풀을 쓰는 것이 가능한 것은 String은 immutable이기 때문입니다. (immutable은 여기 참조)다만, 2,3,4와 같이 읽어들인 String을 풀에 등록시켜서 써야 할 때가 있습니다. 동일한 user에 대해서 어떤 로직에 synch를 걸어야 할 경우가 있죠.(sync ... more

Linked at 건실성실착실 3실 청년! : .. at 2011/10/02 18:30

... otype 패턴으로 객체가 생성되는 케이스도 있을 수 있습니다. 자세한 것은 요기를 참고해주세요.^^4. 가능하면 Immutable로 만드는 게 좋습니다. 이건 요기를 참고해주세요. 2. Map 이나 List 등 Container 사용을 자제하자.위와 같은 Location을 맵으로 만들면 다음과 같이 ... more

Commented at 2008/03/17 10:10
비공개 덧글입니다.
Commented by taoist at 2010/09/08 00:12
좋은 글 감사합니다. 출처를 표기하고 좀 퍼가도 되죠?
Commented by 삼실청년 at 2010/09/09 19:21
네이버에만 안 퍼가면 된다! 가 라이센스 정책입니다.
도움이 되셨길~~
Commented by 야채소 at 2010/11/15 16:14
이해하기 쉬운 설명 감사합니다! 해당 글 안에서 원하는 정보를 모~~두 얻었네요.
Commented by 삼실청년 at 2010/11/17 18:41
도움이 되셨다니 다행이네요.
좋은 하루 되세요^^
Commented by 박성수 at 2016/12/07 14:15
좋은글 감사합니다! 퍼갈게요!

:         :

:

비공개 덧글

◀ 이전 페이지          다음 페이지 ▶