Java

[Java]Immutable Object(불변객체)

📝 작성 : 2020.07.03  ⏱ 수정 : 
728x90

Mutable Object? Immutable Object?

Immutable Object(불변객체)는 객체 생성 후 그 상태를 바꿀 수 없는 개체입니다. 다르게 말하면 할당 된 데이터를 바꿀 수 없는 객체입니다.
예를 들어 String[] arr = {"a", "b", "c"}; arr[1] = "z";라고 코드를 작성하면 b가 z로 바뀌는 것을 알 수 있습니다. 이는 할당 된 데이터를 바꾼 것이 되므로 Mutable Object(가변객체)입니다.

불변객체의 가장 대표적인 예로는 String, Integer, Boolean 등이 있습니다.(주의 할 점은 int, boolean등 원시타입이 아니라는 것입니다. 원시타입은 객체가 아닙니다.)

String을 예로 들어 보겠습니다. String str = "a"; str = "b"; 라고 했을 때 b객체를 만들고 이를 str이 참조하도록 하는 것 이므로 이는 a라는 데이터가 b로 바뀌는 것이 아닙니다.

Immutable Object의 장단점

장점

  • 한번 생성되면 값이 바뀌지 않으므로 객체에 대한 신뢰도가 높아집니다.
  • Thread-Safe 합니다. 따라서 멀티 스레드 환경에서 안전하게 사용 가능합니다.
  • 생성자, 접근메소드에 대해 방어복사를 할 필요가 없습니다.

    단점

  • 매번 새로운 객체가 필요합니다. => 메모리 누수 및 성능저하를 발생시킬 수 있습니다.

Immutable class 구현

  • 클래스를 상속받을 수 없도록 final로 선언합니다.
  • 클래스의 멤버변수를 final로 선언합니다.
  • 클래스의 멤버변수를 private로 선언합니다.
  • 멤버변수에 대해 setter 메서드를 만들지 않습니다.
  • 생성자를 통해 깊은 복사를 할 수 있도록 만듭니다.
  • getter 메서드에서 객체를 참조 할 수 없도록 객체를 깊은 복사하여 리턴합니다.

public final class ImmutableClass { 
    private final int age; 
    private final String name; 
    private final HashMap<String, Object> map;

    public ImmutableClass(int age, String name, HashMap<String, Object> map) {
      this.age = age;
      this.name = name;

      HashMap<String, Object> tmpMap = new HashMap<>();
      String key;
      Iterator<String> it = map.keySet().iterator();
      while (it.hasNext()) {
          key = it.next();
          tmpMap.put(key, map.get(key));
      }
      this.map = tmpMap;
    }

    public int getAge() {
      return age;
   }

    public String getName() {
      return name;
    }

    public HashMap<String, Object> getMap() {
      // return map;
      return (HashMap<String, Object>) map.clone();
    }

    public static void main(String[] args) {
      String name = "dhlee";
      int age = 27;

      HashMap<String, Object> inKorea = new HashMap<>();
      inKorea.put("age in Korea", 29);
      inKorea.put("name in Korea", "이동현");

      Exam04_ImmutableClass ic = new Exam04_ImmutableClass(age, name, inKorea);
      System.out.println("age : " + age + ", name : " + name + ", inKorea : " + inKorea);
      System.out.println("ic.age : " + ic.getAge() + ", ic.name : " + ic.getName() + ", ic.getMap : " + ic.getMap());
      System.out.println("age == ic.getAge() ? " + (age == ic.getAge()));
      System.out.println("name == ic.getName() ? " + (name == ic.getName()));
      System.out.println("inKorea == ic.getMap() ? " + (inKorea == ic.getMap()));

      System.out.println("10 years later");

      age = 37;
      name = "Lee DongHyun";
      inKorea.replace("age in Korea", 39);
      inKorea.replace("name in Korea", "멋쟁이 동현");
      System.out.println("ic.age : " + ic.getAge() + ", ic.name : " + ic.getName() + ", ic.getMap : " + ic.getMap());

      System.out.println("get a job!!");

      HashMap<String, Object> testMap = ic.getMap();
      testMap.put("job", "programmer");
      System.out.println(ic.getMap());    
    }  
}  

결과
age : 27, name : dhlee, inKorea : {age in Korea=29, name in Korea=이동현} 
ic.age : 27, ic.name : dhlee, ic.getMap : {age in Korea=29, name in Korea=이동현} 
age == ic.getAge() ? true 
name == ic.getName() ? true 
inKorea == ic.getMap() ? false
10 years later
ic.age : 27, ic.name : dhlee, ic.getMap : {age in Korea=29, name in Korea=이동현}
get a job!!
{age in Korea=29, name in Korea=이동현}

여기서 주목해야할 부분은 생성자에서 HashMap의 처리와 HashMap 객체의 getter메서드 부분입니다.

만약 getter메서드를 단순히 return map;이라고 했다면

age : 27, name : dhlee, inKorea : {age in Korea=29, name in Korea=이동현}
ic.age : 27, ic.name : dhlee, ic.getMap : {age in Korea=29, name in Korea=이동현}
age == ic.getAge() ? true
name == ic.getName() ? true
inKorea == ic.getMap() ? false
10 years later
ic.age : 27, ic.name : dhlee, ic.getMap : {age in Korea=29, name in Korea=이동현}
get a job!!
{age in Korea=29, name in Korea=이동현, job=programmer}

생성자에서 this.map = map;이라고 했다면
age : 27, name : dhlee, inKorea : {age in Korea=29, name in Korea=이동현} 
ic.age : 27, ic.name : dhlee, ic.getMap : {age in Korea=29, name in Korea=이동현} 
age == ic.getAge() ? true 
name == ic.getName() ? true 
inKorea == ic.getMap() ? false 
10 years later
ic.age : 27, ic.name : dhlee, ic.getMap : {age in Korea=39, name in Korea=멋쟁이 동현} 
get a job!!
{age in Korea=39, name in Korea=멋쟁이 동현}

생성자를 this.map = map;, getter메서드를 return map;이라고 했다면
age : 27, name : dhlee, inKorea : {age in Korea=29, name in Korea=이동현} 
ic.age : 27, ic.name : dhlee, ic.getMap : {age in Korea=29, name in Korea=이동현} 
age == ic.getAge() ? true 
name == ic.getName() ? true 
inKorea == ic.getMap() ? true 
10 years later
ic.age : 27, ic.name : dhlee, ic.getMap : {age in Korea=39, name in Korea=멋쟁이 동현} 
get a job!!
{age in Korea=39, name in Korea=멋쟁이 동현, job=programmer}

이런 결과가 나옵니다. 따라서 Mutable Object가 있는 경우 생성자 및 getter 메서드를 만드는데 주의를 해야 합니다.

public 멤버변수

간혹 Immutable Class를 이렇게 만드는 경우를 볼 수 있습니다.

final class ImmutableClass {
    [public | default] final int age;

    public ImmutableClass(int i) { this.i = i; }
}

이런 경우 외부에서 값을 변경하려고 하면 `cannot assign a value to final variable`이라는 오류가 발생합니다.

하지만 Map, List등 Mutable Object는 값이 변경될 수 있습니다.

final class ImmutableClass {
    public final HashMap<String, Object> map;

    public ImmutableClass(HashMap<String, Object> map) {
        HashMap<String, Object> tmpMap = new HashMap<>();
        String key;
        Iterator<String> it = map.keySet().iterator();
        while (it.hasNext()) {
            key = it.next();
            tmpMap.put(key, map.get(key));
        }
        this.map = tmpMap;
    }
}

아까 위에서 확인했던 생성자입니다.
public static void main(String[] args) {
    HashMap<String, Object> map = new HashMap<>();
    map.put("name", "dhlee");
    map.put("age", 29);

    ImmutableClass ic = new ImmutableClass(map);
    System.out.println(ic.map);

    ic.map.put("job", "programmer");
    System.out.println(ic.map);
}

결과는 어떻게 될까요?
{name=dhlee, age=29}
{name=dhlee, job=programmer, age=29}

그러므로 Mutable Object가 있는 경우는 멤버변수를 꼭! private로 선언해야합니다.

마무리

java의 reflection을 이용하면 Immutable Object도 변경 가능하다고 합니다.
이거에 대해서는 나중에 기회가 되면 포스팅 하도록 하겠습니다.

반응형

'Java' 카테고리의 다른 글

[Java] String.split(".")  (0) 2020.07.11
[Java] List 중복 제거하기  (0) 2020.07.09
[Java] String = ""; vs new String("");  (0) 2020.07.01
[Java] 배열을 ArrayList로 변환  (0) 2020.06.30
[Java] 자바 날짜, 시간 API의 형변환 방법  (0) 2020.06.29