[개발지식] 참조방식 관점에서 본 Immutable/Mutable 특성의 차이점과 활용방안(*오토박싱으로 인한 값 비교 유의점까지)

Hyo Kyun Lee·2025년 6월 12일
0

개발지식

목록 보기
90/91

1. 개요

자동화 구축 작업을 진행하면서 Immutable, Mutable 특성 및 내부적으로 데이터를 저장하는 과정을 한번 정리할 필요가 있을 것으로 생각하여 정리한다.

2. Immutable의 정의 - 변수와 데이터 할당

자바에서 참조형 변수(객체)는 힙 영역에 할당되고, 스택 영역에 지역변수나 스레드(실행을 유발하는 동작/메소드)가 할당된다.

예를 들어 아래와 같이 변수를 선언했다고 하자.

Integer i = 1;
i = 3;

힙 영역에 실제 데이터인 1이 할당되고 이를 참조변수 i가 스택 영역에서 참조받는데, 3을 할당하면 객체의 값이 변경된 것이 아니라, 힙 영역에 새로운 객체를 생성하고 이 객체에 대한 참조값을 변경한다.

public final class Integer extends Number implements Comparable<Integer> {
    private final int value;
    ...
}

그리고 기존 1을 가진 주소값은 gc에 의해 사라지게 되며, 이처럼 스택의 데이터값을 직접적으로 "수정"할 수 없고 새로운 객체 혹은 데이터를 생성하여 해당 데이터를 참조하는 "참조주소를 바꾸는" 방식이 발생하는 경우를 Immutable이라 한다.

참고로 위 Interger 변수 할당 방식을 보면 알 수 있듯이 final로 선언하므로 할당된 변수의 "값"을 바꿀 수는 없다.

3. Immutable은 힙에서 새로운 객체를 생성하므로 참조하는 값을 바꾸려면 "재할당" 해주어야 한다.

이번에 리팩토링하면서 다시 한번 정리했던 개념이 바로 이 부분이다.

poplntTermEngName
  .replaceAll("baljeonso", " Plant ")
  .replaceAll("Baljeonso", " Plant ")
  .replaceAll("taeyanggwang", " Solar Power ")
  .replaceAll("Taeyanggwang", " Solar Power ")
  .replaceAll("Baiomaeseu", " Bioelectric Power ")
  .replaceAll("baiomaeseu", " Bioelectric Power ")
  .replaceAll("baio", " Bioelectric Power ")
  .replaceAll("Baio", " Bioelectric Power ")
  .replaceAll("pungnyeok", " Wind Power ")
  .replaceAll("Pungnyeok", " Wind Power ");

위와 같이 poplntTermEngName 이라는 변수에 할당된 데이터를 replaceAll하여 바꾼다고 할 때, poplntTermEngName이 String으로 선언되어있어 해당 데이터를 직접적으로 변경하는 것은 불가능하였다.

따라서 아래와 같이

poplntTermEngName = poplntTermEngName
  .replaceAll("baljeonso", " Plant ")
  .replaceAll("Baljeonso", " Plant ")
  .replaceAll("taeyanggwang", " Solar Power ")
  .replaceAll("Taeyanggwang", " Solar Power ")
  .replaceAll("Baiomaeseu", " Bioelectric Power ")
  .replaceAll("baiomaeseu", " Bioelectric Power ")
  .replaceAll("baio", " Bioelectric Power ")
  .replaceAll("Baio", " Bioelectric Power ")
  .replaceAll("pungnyeok", " Wind Power ")
  .replaceAll("Pungnyeok", " Wind Power ");

새로운 데이터(객체)를 "다시 바라보게 해주어야", 즉 "재할당 해야" 비로소 바뀐 객체의 값을 바라보게 된다.

기존의 객체는 더이상 바라보지 않고 gc에 의해 제거된다.

4. Immutable 관점에서 바라본 equals, ==의 차이점

기본적으로 ==은 힙에 생성된 데이터, 즉 객체를 동일하게 참조하고 있느냐 다시 말해 "동일한 객체이냐"를 검증한다.

이와 달리 equals는 할당된 변수값만 비교한다, 즉 객체가 달라도 내용이 같으면 같은 것으로 간주한다.

이때 String, Integer 등은 immutable하기 때문에 값이 바뀌지 않으므로, ==으로 엄격하게 객체값을 비교할 필요 없이 .equals()로 비교해도 무방하며 안전한 비교를 보장할 수 있다.

String a = "hello";
String b = new String("hello");
System.out.println(a == b);      // false
System.out.println(a.equals(b)); // true

5. 오토박싱

더 나아가 보자.

기본형(primitive) 데이터를 자동으로 래퍼 클래스(wrapper class) 로 변환해주는 것
즉, 자바가 int ↔ Integer 같은 변환을 자동으로 해주는 기능을 오토박싱(-> Wrapper)/언박싱(-> Primitive)이라 한다.

예를 들어

Integer a = 100;
Integer b = 100;
System.out.println(a == b); // ✅ true

Integer x = 1000;
Integer y = 1000;
System.out.println(x == y); // ❌ false

자바는 -128 ~ 127 범위의 Integer 값은 캐시(cache)하는 특성이 있기에, 해당 범위의 값은 같은 객체(같은 주소)를 재사용해서 ==도 true가 되지만, 그 외의 값은 새 객체 생성이기 때문에 ==는 false로 나타난다.

따라서 List에 들어가는 값을 오토박싱하는 자바의 특성상 반드시 equals로 "내용비교"를 해주어야 예상한 결과를 얻을 수 있다.

6. 결론

이처럼 Immutable한 변수 할당은 결국 재할당이 필요하므로, Call By Value가 주를 이루는 로직에서 유의하며 활용해야 할 것이다.

7. 참고자료

같이 공부하면서 정리 - https://velog.io/@gyrbs22/Immutable

0개의 댓글