JAVA - 불변 ( Immutability )

TopOfTheHead·2025년 9월 25일

자바 ( JAVA )

목록 보기
13/23

final 키워드 :
변수 , method , class에 선언하여 활용

final은 불변하지 않다 ( 중요 )
▶ 오직 재할당을 금지하는 역할만 수행

  • effectively final
    final로 명시적으로 선언하지 않아도, 변수에 한번만 값이 할당되고 그 이후 재할당이 전혀 일어나지않는 변수에 한해 람다식 또는 익명클래스에 의해 final로 간주되는 기능

  • final 변수 :
    객체참조변수 , 변수에 선언하며 한번 값이 할당 될 경우 그 이후에 재할당을 금지
    상수와 의미가 유사

    객체참조변수에 선언 시 참조대상객체주소가 고정되어있으므로 할당이 불가능하지만, 객체 내부 값은 변경가능
    객체불변객체로 만드는 경우 내부 인스턴스 변수에도 final 선언

    final을 통해 변수불변속성을 부여할 수 있으나, 일부 자료구조 객체( Map , Set 등 )의 경우 보장이 안되므로 자료구조객체.of(값)을 통해 불변성을 보장하는 자료구조 객체를 생성

    상수와 차이점?
    final 변수 : 각 클래스 객체에서 인스턴스 변수로서 메모리 할당되어 Heap 영역에 저장
    객체생성시점부터 프로그램 수명주기 동안 메모리 할당

    상수 ( static final ) : 유일한 클래스 변수로서 Static 영역에서 저장
    Compile Time부터 프로그램 수명주기 동안 메모리 할당



  • final 메소드 :
    。선언된 메소드자식클래스에서 메소드 오버라이드 할 수 없도록 설정.
    ex ) 템플릿 메소드 패턴에서 템플릿메소드자식클래스에서 메소드 오버라이딩을 방지하기위해 사용

  • final 클래스
    。 더 이상 자식클래스를 가질 수 없는 Class
    ▶ 해당 클래스를 다른 클래스에서 상속할 수 없도록 설정

불변 ( Immutablility )

변수 , 객체가 생성된 후에는 상태를 변경할 수 없도록 금지하는 특성
가변( Mutable )과 반대 개념

클래스는 되도록 내부 인스턴스변수final을 선언하는 등 불변으로 만들어야하며 만약 불변으로 만드는게 불가능하면 변경 가능성을 최소화
가변적으로 만들어야하는 타당한 이유가 있는게 아니면 되도록 불변으로 구축

  • 변수불변성이 없는 경우

    ex ) JSvar type
    초기값 정의 없이 변수만 선언 및 사용가능
    var a;만 선언하고 값을 할당안하고 호출 시 undefined 도출
    불변성이 있는 경우 반드시 이 지정되어야함.

JAVA불변변수 , 불변객체

  • 불변 변수 ( Immutable Variable )
    변수 앞에 final 키워드를 선언
    변수재할당을 금지하여 불변특성을 부여하여 사이드이펙트를 사전에 방지

    객체참조변수에 선언 시 참조대상객체주소가 고정되어있으므로 할당이 불가능하지만, 객체 내부 값은 변경가능
    객체불변객체로 만드는 경우 내부 인스턴스 변수에도 final 선언

  • 불변 객체 ( Immutable Object )
    클래스인스턴스변수final을 선언 및 생성자를 통해 값 할당하여 final객체참조변수에 선언한 불변객체를 생성
    ▶ 생성 후 그 상태 ( = 인스턴스변수 )를 변경할 수 없는 객체

    불변객체의 복제는 보통 copyOf()를 사용한다.

    일부 자료구조 인터페이스( Map , Set 등 )의 경우 자료구조객체.of(값)을 통해 불변성을 보장하는 자료구조 불변객체를 생성

    생성자를 통해 변수에 값을 전달하여 불변객체를 생성 시 이후 내부 불변변수 수정이 불가능
    불변객체상태 변경 시 새 객체를 생성하여 재할당하는 방식으로 방식으로 변경해야한다.
    상태변경시 마다 새 객체를 생성하지만 수명이 짧으므로 GC의 청소대상이 되기 쉽다.
 // 불변객체
class Money{
	private final int amount; // 인스턴스 변수에 final 선언 
	public Money(int amount){
		this.amount = amount;
	}
}
// 선언
final Money m = new Money(2000);
  • Java에서 변수final을 선언 시 불변성을 보장할 수 없는 이유
    final은 선언된 변수 , 객체참조변수에 선언 시 재할당을 금지하는 기능을 수행.

    자료구조 객체 ( Map , Set 등 )의 경우 을 변경하는게 아닌 객체.add(데이터)객체에 요소를 추가하므로 final에 의해 불변성을 보장받을 수 없음
    자료구조객체.of(값)을 통해 불변성을 보장하는 자료구조 객체를 생성
final Set<Integer> Set1 = new HashSet<Integer>();
Set<Integer> Set2 = Set.of(1, 2);
Set1.add(3); // 불변객체가 아니므로 데이터 삽입 수행됨
Set2.add(3); // 불변객체에 데이터를 넣으려고 시도했으므로 오류 발생

final 키워드 :
변수 , method , class에 선언하여 활용

final은 불변하지 않다 ( 중요 )
▶ 오직 재할당을 금지하는 역할만 수행

  • final 변수 :
    객체참조변수 , 변수에 선언하며 한번 값이 할당 될 경우 그 이후에 재할당을 금지
    상수와 의미가 유사

    객체참조변수에 선언 시 참조대상객체주소가 고정되어있으므로 할당이 불가능하지만, 객체 내부 값은 변경가능
    객체불변객체로 만드는 경우 내부 인스턴스 변수에도 final 선언

    final을 통해 변수불변속성을 부여할 수 있으나, 일부 자료구조 객체( Map , Set 등 )의 경우 보장이 안되므로 자료구조객체.of(값)을 통해 불변성을 보장하는 자료구조 객체를 생성

    상수와 차이점?
    final 변수 : 각 클래스 객체에서 인스턴스 변수로서 메모리 할당되어 Heap 영역에 저장
    객체생성시점부터 프로그램 수명주기 동안 메모리 할당

    상수 ( static final ) : 유일한 클래스 변수로서 Static 영역에서 저장
    Compile Time부터 프로그램 수명주기 동안 메모리 할당



  • final 메소드 :
    。선언된 메소드자식클래스에서 메소드 오버라이드 할 수 없도록 설정.
    ex ) 템플릿 메소드 패턴에서 템플릿메소드자식클래스에서 메소드 오버라이딩을 방지하기위해 사용

  • final 클래스
    。 더 이상 자식클래스를 가질 수 없는 Class
    ▶ 해당 클래스를 다른 클래스에서 상속할 수 없도록 설정

불변의 특징
。코드를 안전하고 예측가능하며 안정적인 서비스개발유지보수가 용이하도록 하는 역할을 수행


1. Thread Safe
불변 변수병렬프로그래밍에 유용하며 동기화를 고려하지 않아도 됨.
▶ 여러 스레드들로부터 Concurrent하게 접근되어 Race Condition이 발생해도 값이 변경되지않으며 항상 동일한 값을 반환하므로

런타임이 아니더라도 여러 스레드에서 Concurrent하게 접근하는 변수에 대해 불변 속성을 부여 시 해당 변수human error로 수정이 될 경우 사전에 컴파일러에 의해 검출되어 수정을 예방하는 용도로 활용되기도 함


2. 다른 사람이 작성한 함수를 예측가능하며 안전하게 사용가능
불변이 보장된 함수의 경우 위험없이 이용 가능


3. Side Effect로 인한 피해를 최소화
Side Effect : 변수의 값 , 상태 등의 변화가 발생하는 효과
객체를 여러 곳에서 수정 시 상태를 예측하기가 매우 어렵다.

불변객체는 기본적으로 내부 인스턴스변수의 값 변경을 차단하여 상태 변경이 금지되어있으므로 안전하게 재사용이 가능하다.


4. 원자성( Atomicity ) 보장
불변객체에 대한 메소드 실행 중 예외가 발생 시 메소드 호출 전 상태로 복구를 보장
DB 트랜잭션원자성과 유사

불변객체의 특성 이용
값 수정 성공 시 수정된 값생성자로 전달하여 생성한 새 객체로 업데이트
예외가 발생하여 값 수정 실패 시 기존 객체를 그대로 유지

// 불변객체
class Money{
	private final int amount; 
	public Money(int amount){
		this.amount = amount;
	}
	public int amount(){
		return amount;
	}
	public Money minus(int targetAmount){
		if ( this.amount < targetAmount ) throw new IllegalArgumentException("잔액부족");
		return new Money(this.amount - targetAmount);
	}
	@Override
	public String toString() {
		return "Money{" + "amount=" + amount + '}';
	}
}
public class atomicity {
	public static void main(String[] args) {
		Money m2 = new Money(10_000);
		try {
            // 성공 시 새 객체로 업데이트
			m2 = m2.minus(20_000);
		} catch(IllegalArgumentException e){
			System.out.println(e.getMessage());
		}
		System.out.println(m2.amount());
	}
}

。 case 2 : Money m2 : 10_000 - 20_000 = -10_000으로 예외가 발생되어 새 객체로 업데이트되지 않았으므로 기존 객체 유지
원자성 보장


5. 불변객체Set , Map , Cache요소로 저장 시 안전하게 사용이 가능
Map , Set의 경우 Key - Value 쌍 데이터를 저장.

해시값을 사용하는 자료구조에서 Key Type클래스로 지정 및 Key 객체를 통해 Value를 찾을 경우 해당 클래스 내에 정의된 equals() , hashCode() 메소드를 통해 동일객체여부를 판단

MapKey Type불변객체클래스로 지정 시 안전한 동작을 보장
Class객체equals()를 통해 객체인스턴스변수를 비교 후 동일여부를 판단하므로 Class객체인스턴스변수를 수정하여 상태변경동일 객체가 아니게 되므로 조회가 불가능.
불변객체로 지정 시 내부 인스턴스변수를 변경하는 상태변경 자체를 금지하므로 동일 객체로 판단할 수 있게하여 안전하게 사용이 가능하다.

import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
// Map<K,V>에서 Key Type으로 불변 클래스
class Key{
	private final int id1; // 불변변수
	private final int id2; // 불변변수
	public Key(int id1, int id2) {
		this.id1 = id1;
		this.id2 = id2;
	}
	@Override
	public int hashCode() {
    	// 객체 내 인스턴스변수값을 int의 해시값으로 만든 후 return
    	// Hash 자료구조 에서 객체 비교 수행 시 해당 해시값으로 비교
		return Objects.hash(id1,id2);
	}
	@Override
	public boolean equals(Object obj) {
		// 객체 메모리주소값 동일한 경우 true 반환
		if ( this == obj ) return true;
		if ( obj instanceof Key k ) // obj가 Key 클래스의 객체인지 확인 후 Key 클래스로 캐스팅
			// 객체의 내부 불변변수가 동일한 경우 true 반환
			return this.id1 == k.id1 && this.id2 == k.id2;
		// 위 조건을 만족하지 못한 경우 false 반환
		return false;
	}
}
public class ImmutableMap {
	public static void main(String[] args) {
		Map<Key,String> map = new HashMap<Key,String>();
		map.put(new Key(1,1), "lee");
		System.out.println(map.get(new Key(1,1))); // lee 출력
	}
}

6. Garbage Collector의 성능을 증진

불변객체 활용 시 Garbage Collector가 스캔해야하는 메모리영역이 감소하여 성능을 증진.
불변객체를 수정하여 불변객체를 일일이 생성하는 비용보다 불변객체를 사용하여 GC스캔범위가 축소되는 비용이 더 저렴하므로 불변객체를 이용

profile
공부기록 블로그

0개의 댓글