불변 객체
-기본형과 참조형의 공유
자바의 데이터 타입을 가장 크게 보면 기본형(Primitive Type)과 참조형(Reference Type)으로 나눌 수 있다.
- 기본형 : 하나의 값을 여러 변수에서 절대로 공유하지 않는다.
- 참조형 : 하나의 객체를 참조값을 통해 여러 변수에서 공유할 수 있다.
많이 했던 거지만 다시 복습할 겸 해보자!!
//기본형
public class PrimitiveMain {
public static void main(String[] args) {
//기본형은 절대로 같은 값을 공유하지 않는다.
int a = 10;
int b = a; //a => b, 값 복사 후 대입
System.out.println("a=" + a);
System.out.println("b=" + b);
b = 20;
System.out.println("20> ->b");
System.out.println("b=" + b);
System.out.println("a" + a):
}
실행값 :
a=10
b=10
20 -> b
b =20
a =10
-----------------------------------------------------------
//참조형
public class RefMain1_1 {
public static void main(String[] args) {
//참조형 변수는 하나의 인스턴스를 공유할 수 있따.
Address a = new Address("seoul"); //x001
Address b = a; //x001
System.out.println("a=" + a);
System.out.println("b=" + b);
b.setValue("busan"); //b의 값을 부산으로 변경해야함 x001의 value를 부산으로 변경
System.out.println("부산 -> b");
System.out.println("a=" + a);
System.out.println("b=" + b);
}
}
실행값 :
a=Address{value='seoul'}
b=Address{value='seoul'}
부산 -> b
a=Address{value='busan'}
b=Address{value='busan'}
- 공유 참조와 사이드 이펙트
사이드 이펙트(SIde Effect)는 프로그래밍에서 어떤 계산이 주된 작업 외에 추가적인 부수 효과를 일으키는 것을 말한다.
앞서 b의 값을 부산으로 변경한 코드를 다시 분석해보자.
b.setValue("busan"); //b의 값을 부산으로 변경해야함
System.out.println("부산 -> b");
System.out.println("a=" + a); //사이드 이펙트 발생
System.out.println("b=" + b);

이렇게 주된 작업 외에 추가적인 부수 효과를 일으키는 것을 사이드 이펙트라 한다. 프로그래밍에서 사이드 이펙트는 보 통 부정적인 의미로 사용되는데, 사이드 이펙트는 프로그램의 특정 부분에서 발생한 변경이 의도치 않게 다른 부분에 영 향을 미치는 경우에 발생한다. 이로 인해 디버깅이 어려워지고 코드의 안정성이 저하될 수 있다.
-사이드 이펙트 해결 방안
생각해보면 문제의 해결방안은 아주 단순하다. 다음과 같이 a 와 a가 처음부터 서로 다른 인스턴스를 참조하면 된다.
Address a = new Address("seoul"); //x001
Address b = new Address("seoul"); //x002
- 불변 객체 - 도입
그런데 사이드 이펙트의 더 근본적인 원인을 고려해보면, 객체를 공유하는 것 자체는 문제가 아니다.
만약 객체의 값을 변경하지 못하게 설계한다면, 사이드 이펙트 자체가 발생하지 않을 것이다
앞서 만들었던 Address 클래스를 상태가 변하지 않는 불변 클래스로 다시 만들어보자.
public class ImmutableAddress {
private final String value; //final로 불변 상수 처리
public ImmutableAddress(String value) {
this.value = value;
}
public String getValue() {
return value;
}
@Override
public String toString() {
return "Address{" +
"value='" + value + '\'' +
'}';
}
}

ImmutableAddress 은 불변 객체이므로 b가 참조하는 인스턴스의 값을 서울에서 부산으로 변경하려면 새로 운 인스턴스를 생성해서 할당해야 한다.
불변이라는 단순한 제약을 사용해서 사이드 이펙트라는 큰 문제를 막을 수 있다.
- 불변 객체는 값을 변경할 수 없다. 따라서 불변 객체의 값을 변경하고 싶다면 변경하고 싶은 값으로 새로운 불변 객체를 생성해야 한다. 이렇게 하면 기존 변수들이 참조하는 값에는 영향을 주지 않는다.
public class MemberMainV2 {
public static void main(String[] args) {
ImmutableAddress address = new ImmutableAddress("서울");
MemberV2 memberA = new MemberV2("회원A", address);
MemberV2 memberB = new MemberV2("회원B", address);
//회원 A, 회원 B의 처음 주소는 모두 서울
System.out.println("memberA" + memberA);
System.out.println("memberB" + memberB);
//회원 B의 주소를 부산으로 변경해야함
memberB.setAddress(new ImmutableAddress("부산")); //새로운 인스턴스에 "부산" 값을 입력하여 주소에 세팅
System.out.println("memberA" + memberA);
System.out.println("memberB" + memberB);
}
}
사이드 이펙트가 발생하지 않는다.
- 불변 객체 - 값 변경
public class ImmuableMyDate {
private final int year;
private final int month;
private final int day;
public ImmuableMyDate(int year, int month, int day) {
this.year = year;
this.month = month;
this.day = day;
}
public int getYear() {
return year;
}
public ImmuableMyDate withYear(int newYear) {
return new ImmuableMyDate(newYear, month, day);
}
public ImmuableMyDate withMonth(int newMonth) {
return new ImmuableMyDate(newMonth, year, day);
}
public ImmuableMyDate withDay(int newDay) {
return new ImmuableMyDate(newDay, month, year);
}
public int getMonth() {
return month;
}
public int getDay() {
return day;
}
@Override
public String toString() {
return "ImmuableMyDate{" +
"year=" + year +
", month=" + month +
", day=" + day +
'}';
}
}
- with 메서드는 새로운 값으로 객체의 복사본을 생성. 이는 원본 객체를 변경하지 않고 새로운 상태를 가진 새 객체를 반환.
- 이 방식은 불변성을 유지하면서 객체의 상태를 "변경"할 수 있게 해준다. 실제로는 새 객체를 만들어 반환하는 것이므로 원본 객체는 변하지 않습니다.
public class ImmuableMyDateMain {
public static void main(String[] args) {
//date1 = x001, date2 = x002
ImmuableMyDate date1 = new ImmuableMyDate(2024, 1, 1);
ImmuableMyDate date2 = date1;
System.out.println(date1);
System.out.println(date2);
System.out.println("2025 -> date1");
date1 = date1.withYear(2025); //x002 본인의 값을 기반으로 새 객체를 만들어 반환해준 값을 받음 //반환값 무조건 받아야됨!!
System.out.println(date1); //x002
System.out.println(date2); //x001
}
}
- date1.withYear(2025)는 새로운 ImmuableMyDate객체를 생성하여 반환하며, 이 새 객체의 참조를 date1에 할당합니다. 이는 원본 객체를 변경하지 않고 새로운 상태를 갖는 객체를 만든다.
- date2는 여전히 원래의 객체(2024)를 참조하고 있어, date1과 date2가 서로 다른 객체를 가리키게 됩니다. 이는 불변 객체의 특성을 잘 보여주는 예시입니다.
주의할 점 :
- date1 = date1.withYeer(2025) 이 라인은 매우 중요합니다. withYear()메서드는 새로운 객체를 생성하여 반환하므로, 이 새 객체의 참조를 반드시 변수에 할당해야 합니다.
- 만약 date1.withYear(2025)만 작성하고 그 결과를 변수에 할당하지 않으면, 새로 생성된 객체는 어디에도 참조되지 않아 즉시 가비지 컬렉션의 대상이 됩니다. 결과적으로 원하는 "변경"이 이루어지지 않습니다.
정리 : 불변 객체를 사용할 때는 메서드 호출의 결과를 새 변수에 할당하거나, 기존 변수를 업데이트해야한다! 그렇지 않으면 의도한 변경사항이 적용되지 않는다!
🙇♂️ 결론
불변 객체에 대해 공부하기 위해선 기본형과 참조형 변수에 대해 확실히 알았어야 했다.
어느 정도 개념을 알았다고 생각했다만, 자유자재로 활용하기에는 조금 부족하단 생각이 들었다.
개념 정리된 글을 막연히 계속 보기보다는 , 내가 간단한 예제를 직접 구현하는 것이 더 도움이 될 거 같다.
조금 더 효율적으로 복습하도록 해야겠다.
화이또 ㅎ
'Language > ☕ Java' 카테고리의 다른 글
| [Java] 예외 처리 (Exception Handling) (0) | 2024.08.25 |
|---|---|
| [Java] String 클래스 (0) | 2024.08.23 |
| [Java] Object 클래스 (0) | 2024.08.19 |
| [Java] 다형성 역할과 구현 예제 (0) | 2024.08.15 |
| [Java] 다형성 2 (0) | 2024.08.14 |