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)
- 생성자로 생성할 때도 깊은 복사 활용
- 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);
}