Java/이론&문법

Mutable & Immutable 객체

Bubbles 2023. 9. 15. 21:45

Mutable Object

  • 생성된 이후에 수정이 가능하며, 이미 존재하는 객체에 재할당이 가능 
  • 값을 변경할 수 있는 메소드 제공
  • Heap 영역에 생성된 객체를 변경할 수 있다. 
  • List, ArrayList, HashMap, StringBuilder, StringBuffer...

 

Immuatable Object

  • String, Boolean, Integer, Float, Long 등
  • 외부에서 객체의 데이터를 변경할 수 없다(heap 영역에서 바꿀 수 없다.)
  • 새 객체를 만들어 reference 값을 주는 재할당만 가능 
  • 신뢰도 보장, 멀티 스레드 환경에서 안전함. 
  • 대신 객체의 값이 할당될 때마다 새로운 객체 필요(메모리 많이 잡아먹음)

 

불변객체로 만드는 방법 

  • Integer, String 등의 API : final 선언을 통해 변경 불가능하게 만들어져 있다.
  • setter와 같이 내부 요소를 변경할 수 있는 메소드 구현하지 말 것 
  •  멤버 변수를 private + final로, class 상속받지 못하도록 class를 final로 선언
    • 단, final이 객체에 붙으면 객체의 속성까지는 관여하지 않는다. 이 부분을 더 방어해야 한다. 
  • 생성자를 private로 선언
  • 가변 객체 타입의 field 변수가 있을 경우,  방어적 복사본 전략을 사용
    • 방어적 복사란?
      • 내부의 객체를 반환할 때 객체의 복사본을 만들어 반환하는 것
      • 복사한 객체를 외부에서 바꿔도 원본 내부 객체가 변경되지 않음
      • 하지만 이 친구는 깊은 복사가 아니다. 
      • 즉 컬렉션같이 요소가 내부에 또 있는 경우, 각 컬렉션의 주소는 다르지만 내부 요소들은 동일하여 복사본에서 내부 요소를 바꾸면 원본에도 영향을 미친다. 
    • 컬렉션 타입의 필드가 있다면
      • ex) private final List<ImmutableSomething> list 라고 해두고 getter만 열어두었다 가정.
        • getter : return list; 라 하자. 
        • 외부에서 list.getList().add(new ImmutableSomething()); 하면 데이터가 추가되어 있다.
      • 컬렉션에서도 add, remove, set등을 막아야 한다. 
        • 생성자에 넣는 컬렉션,  얘를 수정해도 원본에 영향 주지 않아야 함. (완전히 참조 끊어지도록 깊은복사)
        • 직접 getter 이용해서 리스트를 꺼내더라도, 꺼낸 리스트를 수정할 수 없어야 한다. 
      • getList() 할 때 return List.copyOf(list) 
      • 생성자로 생성할 때도 깊은 복사 활용 

 

public final class Main {

    private final int id;
    private final List<Integer> list;
    private final Coffee coffee;

    private Coffee getCoffee() {
        return new Coffee(this.coffee.price, this.coffee.name);
    }
    private int getId() {
        return id;
    }

    private List<Integer> getList() {
        return List.copyOf(list);
    }


    public Main(int id, ArrayList<Integer> list, Coffee coffee) {
        this.id = id;
        // this.list = list; 이렇게 해두면 list 바꿨을 때 Main의 list도 바뀜
        // 깊은 복사 수행 
        List<Integer> temp = new ArrayList<>();
        Iterator<Integer> iter = list.iterator();
        while (iter.hasNext()) {
            temp.add(iter.next());
        }

        this.list = Collections.unmodifiableList(temp);

        this.coffee = new Coffee(coffee.price, coffee.name);
    }

    public static void main(String[] args) {
        int id = 100;
        ArrayList<Integer> list = new ArrayList<>();
        list.add(1);
        list.add(2);

        Coffee coffee = new Coffee(10000, "Compose");

        Main main = new Main(id, list, coffee);
        // main.id = 20; 컴파일 에러
        list.add(3);
        list.set(2, 100);
        coffee = new Coffee(20000, "Starbucks");
        System.out.println(main.list); // 영향을 주지 않음 (완전히 참조 끊어짐)
        System.out.println(main.coffee.name + " " +main.coffee.price); // 그대로 만원 컴포즈

        System.out.println("----------------");
        // main.getList().add(3);
        // main.getList().set(1, 3);
        // main.list.add(3);
        // main.list.set(1, 3);
        // unsupportedOperation exception로 막혀있음
        
        // coffee.price = 2000; 재할당 안됨.
        // coffee.name = "MegaCoffee" 재할당 안됨. 

        System.out.println(main.list);
    }

    private final static class Coffee {
        private final int price;
        private final String name;

        public Coffee(int price, String name) {
            this.price = price;
            this.name = name;
        }

        public int getPrice() {
            return price;
        }

        public String getName() {
            return name;
        }
    }
}

출력결과 

[1, 2]
Compose 10000
----------------
[1, 2]

 


  • ImmutableCollections의 copyOf 사용하기 (Java 9부터) 
    • add, remove뿐 아니라 setter까지 막는다. 
static <E> List<E> copyOf(Collection<? extends E> coll) {
    return ImmutableCollections.listCopy(coll);
}