-
Mutable & Immutable 객체Java/이론&문법 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)
- 생성자로 생성할 때도 깊은 복사 활용
- ex) private final List<ImmutableSomething> list 라고 해두고 getter만 열어두었다 가정.
- 방어적 복사란?
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); }
'Java > 이론&문법' 카테고리의 다른 글
자바 Generic (0) 2023.09.22 java.lang package (0) 2023.09.22 Annotation (0) 2023.09.15 Nested Class (0) 2023.09.15 Java의 Exception (0) 2023.09.15