728x90
반응형
불변 객체(Immutable Object)란?
한 번 생성되면 내부 상태를 변경할 수 없는 객체를 의미합니다. 즉, 객체의 필드 값이 생성 이후 절대 변경되지 않습니다.
1) 불변 객체의 특징
불변 객체를 만들기 위해서는 다음과 같은 원칙을 따라야 합니다.
1 모든 필드는 final로 선언
- final 키워드를 사용하면 객체가 생성된 후 해당 필드를 변경할 수 없습니다.
public final class ImmutablePerson {
private final String name;
private final int age;
}
2. 객체를 변경하는 setter 메서드를 제공하지 않음
- 불변 객체는 객체의 값을 변경할 수 없도록 setter 메서드를 제공하지 않습니다.
public void setName(String name) { // 이런 메서드는 만들면 안 됨!
this.name = name;
}
3. 생성자에서 모든 필드를 초기화
- 객체가 생성될 때 필드 값을 설정하고, 이후에는 변경할 수 없도록 합니다.
public ImmutablePerson(String name, int age) {
this.name = name;
this.age = age;
}
4. 객체의 변경이 필요하면 새로운 객체를 반환
- 기존 객체의 값을 변경할 수 없기 때문에, 변경이 필요할 경우 새로운 객체를 생성하여 반환해야 합니다.
public ImmutablePerson withAge(int newAge) {
return new ImmutablePerson(this.name, newAge);
}
5. clone()을 제공하지 않음
- clone() 메서드는 얕은 복사(Shallow Copy) 를 수행할 수 있기 때문에, 불변 객체에서는 사용하지 않는 것이 일반적입니다.
2) 불변 객체의 장점
1. 멀티스레드 환경에서 안전(Thread-Safe)
- 불변 객체는 동기화(Synchronization) 를 할 필요가 없습니다.
- 여러 스레드에서 동시에 읽기(read)만 수행하고, 값이 변경되지 않기 때문입니다.
ImmutablePerson person = new ImmutablePerson("Alice", 30);
// 여러 스레드에서 동시에 person을 사용해도 안전함.
2. 객체의 상태가 변하지 않아 예측 가능
- 코드의 예측 가능성이 높아져 디버깅이 쉬워지고 유지보수성이 향상됩니다.
ImmutablePerson person1 = new ImmutablePerson("Bob", 25);
ImmutablePerson person2 = person1.withAge(30);
System.out.println(person1); // ImmutablePerson{name='Bob', age=25}
System.out.println(person2); // ImmutablePerson{name='Bob', age=30}
→ person1은 변하지 않고, person2라는 새로운 객체가 생성됨.
3. 불변 객체는 해시 기반 컬렉션(HashMap, HashSet)에서 안전하게 사용 가능
- 불변 객체는 hashCode() 값이 변하지 않기 때문에, HashMap 또는 HashSet에 저장할 때 안전합니다.
Map<ImmutablePerson, String> map = new HashMap<>();
ImmutablePerson person = new ImmutablePerson("Charlie", 40);
map.put(person, "Engineer");
// person의 값이 변하지 않기 때문에, 해시 값도 변하지 않아 안전함.
System.out.println(map.get(person)); // Engineer
3) 불변 객체의 단점
1. 값이 변경될 때마다 새로운 객체가 생성됨
- 값이 변할 때마다 새로운 객체가 생성되므로, 메모리 사용량이 증가할 수 있습니다.
String str1 = "Hello";
String str2 = str1.concat(" World"); // 새로운 객체 생성
System.out.println(str1); // Hello
System.out.println(str2); // Hello World
→ 기존 str1은 변하지 않고, str2라는 새로운 객체가 생성됨.
2. 성능 저하
- 자주 변경되는 데이터를 다룰 경우 객체 생성 비용이 증가할 수 있습니다.
- 예를 들어 String 대신 StringBuilder를 사용하는 것이 성능적으로 유리할 수 있음.
// 불변 객체(String) 사용
String s = "Hello";
s += " World"; // 새로운 String 객체 생성
// 가변 객체(StringBuilder) 사용
StringBuilder sb = new StringBuilder("Hello");
sb.append(" World"); // 기존 객체에서 값 변경 가능 (성능 개선)
3. 불변 객체 만들기 (예제 코드)
불변 객체 예제
public final class ImmutablePerson {
private final String name;
private final int age;
// 생성자를 통해 값 초기화
public ImmutablePerson(String name, int age) {
this.name = name;
this.age = age;
}
// Getter만 제공 (Setter 없음)
public String getName() {
return name;
}
public int getAge() {
return age;
}
// 변경이 필요하면 새로운 객체를 반환
public ImmutablePerson withAge(int newAge) {
return new ImmutablePerson(this.name, newAge);
}
@Override
public String toString() {
return "ImmutablePerson{name='" + name + "', age=" + age + "}";
}
}
사용 예제
public class Main {
public static void main(String[] args) {
ImmutablePerson person1 = new ImmutablePerson("John", 25);
System.out.println(person1); // ImmutablePerson{name='John', age=25}
ImmutablePerson person2 = person1.withAge(30); // 새로운 객체 생성
System.out.println(person2); // ImmutablePerson{name='John', age=30}
// 기존 객체는 변경되지 않음
System.out.println(person1); // ImmutablePerson{name='John', age=25}
}
}
가변 객체(Mutable Object)란?
객체 내부 상태를 변경할 수 있는 객체를 의미합니다.
가변 객체의 특징
- 필드 값 변경 가능 → setter 메서드 제공
- 객체의 상태를 변경할 수 있음
- 동기화 문제 발생 가능 → 여러 스레드에서 동시에 접근하면 일관성이 깨질 가능성이 있음
가변 객체의 장점
- 객체를 계속 수정할 수 있어 성능이 좋음 → 새로운 객체를 생성할 필요 없음
- 메모리 사용량 절약 → 동일 객체를 재사용 가능
- 코드가 유연함 → 필요에 따라 값을 자유롭게 변경 가능
가변 객체의 단점
- Thread-Safety 문제 → 여러 스레드에서 수정할 경우 동기화 필요
- Side Effect 발생 가능 → 참조를 공유할 경우 예측하지 못한 값 변경 발생 가능
- 디버깅이 어려움 → 상태 변경이 많으면 추적이 어려움
가변 객체 예제 코드
public class MutablePerson {
private String name;
private int age;
// 생성자
public MutablePerson(String name, int age) {
this.name = name;
this.age = age;
}
// Getter & Setter 제공
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "MutablePerson{name='" + name + "', age=" + age + "}";
}
}
사용 예시
public class Main {
public static void main(String[] args) {
MutablePerson person = new MutablePerson("Alice", 20);
System.out.println(person); // MutablePerson{name='Alice', age=20}
// 값 변경
person.setAge(25);
System.out.println(person); // MutablePerson{name='Alice', age=25}
}
}
→ person 객체가 그대로 유지되며 내부 상태만 변경됨
불변 객체 vs. 가변 객체 비교
비교 | 불변 객체 (Immutable) | 가변 객체 (Mutable) |
상태 변경 가능 여부 | 불가능 (값 변경 시 새로운 객체 생성) | 가능 (Setter를 통해 변경 가능) |
Thread-Safe | 안전 | 동기화 필요 |
메모리 사용 | 증가 (새로운 객체 생성) | 절약 (기존 객체 수정) |
성능 | 상대적으로 느림 (새로운 객체 생성 비용) | 빠름 (기존 객체 수정 가능) |
예제 | String, Integer, LocalDateTime | StringBuilder, ArrayList |
불변 객체를 사용할 때의 대표적인 Java 클래스
Java에서는 몇몇 클래스가 기본적으로 불변 객체(Immutable Object) 로 설계되어 있습니다.
불변 객체의 대표적인 예
Java에는 불변 객체로 설계된 클래스들이 있습니다.
클래스 | 설명 |
String | 문자열 변경 불가능 |
Integer, Double, Float, Long, Short, Byte, Boolean | Wrapper 클래스 |
LocalDate, LocalDateTime, Instant | 날짜 관련 클래스 (Java 8+) |
BigDecimal, BigInteger | 수학 연산을 위한 클래스 |
예제: String 클래스는 불변 객체
String s1 = "Hello";
String s2 = s1.concat(" World"); // 새로운 객체 생성
System.out.println(s1); // Hello
System.out.println(s2); // Hello World
언제 불변 객체를 사용해야 할까?
- 멀티스레드 환경에서 데이터 무결성을 유지하고 싶을 때
- 객체의 불변성을 보장해야 할 때 (데이터 무결성이 중요할 때)
- 값이 변경될 필요가 없는 경우
- 해시 기반 컬렉션(HashMap, HashSet)에서 안전하게 사용해야 할 때
언제 가변 객체를 사용해야 할까?
- 값을 자주 변경해야 할 때
- 성능이 중요한 경우 (객체 생성 비용 절감)
- 동기화 이슈를 직접 관리할 수 있을 때
결론
- 불변 객체(Immutable Object) 는 안전성과 예측 가능성이 높지만 성능이 낮음.
- 가변 객체(Mutable Object) 는 유연성과 성능이 뛰어나지만 Thread-Safety 문제가 발생할 수 있음.
728x90
반응형
'프로그래밍 > JAVA' 카테고리의 다른 글
Java 리스트(List), Map 의 깊은 복사 완벽 정리 (+ 예제 코드) (0) | 2025.03.03 |
---|---|
Java 깊은 복사 vs 얕은 복사 완벽 정리 (+ 예제 코드) (0) | 2025.03.03 |
객체 지향 프로그래밍(OOP)란? 객체 지향 프로그래밍(OOP) vs 함수형 프로그래밍(FP) 차이점 (1) | 2025.03.03 |
Java 자주 사용되는 디자인 패턴 정리! (0) | 2025.03.03 |
Java tryLock() 사용 방법 (Java ReentrantLock) (0) | 2025.03.03 |
댓글