[java] Immutability 불변성 / 불변객체에 대해

CodeKong의 기술 블로그·2024년 3월 23일
2

JAVA

목록 보기
2/5
post-thumbnail

Java에서 Immutability(불변성)은

객체가 한 번 생성된 후 그 상태를 변경할 수 없음

을 의미합니다.


불변 객체의 특징 / 장점

불변 객체가 한 번 생성되면, 그 내부 상태는 전혀 변경될 수 없습니다.

이러한 특징으로 여러 장점을 가질 수 있습니다.

1.재사용성

같은 값을 갖는 객체가 필요할 때 객체를 새로 생성할 필요가 없습니다.

2. 스레드 안전

여러 스레드에서 동시에 사용되어도 상태 변경이 불가하기 때문에 안전합니다.


불변 객체의 예

불변 객체의 대표적인 예로는 String이 있는데요, 다음과 같은 예시가 있습니다.

String str = "test";
str = "thing";

언뜻 보면, str이라는 String 객체가 변한 것처럼 보일 수 있습니다.

하지만 "thing"이라는 새로운 String 객체(불변)를 만들어 str에 할당한 것입니다.


불변 객체의 생성

커스텀으로 불변 객체는 어떻게 만들어야 할까요?

먼저, 불변한 객체를 만든다는 것은 여러가지 의미를 내포합니다.

1. final class

해당 객체를 상속받아 메소드를 오버라이드 하여 상태를 변경하는 것을 방지해야 합니다.

Animal 클래스와 Animal을 상속받는 Dog 클래스가 있습니다.

Animal 클래스는 필드가 final이기 때문에 언뜻보면 불변객체인 것처럼 보입니다.

Animal animal = new Dog("먹이")

처럼 업캐스팅으로 객체를 생성해줍니다.

이해를 돕기위한 참조 객체의 간단한 도식도 입니다.
여기서 animal의 getFood를 호출하면 업케스팅되었기 때문에 "먹이" 가 출력됩니다.

Dog dog = (Dog) animal;

여기서 다운캐스팅을 시켜준다면 도식도는 다음과 같아집니다.

Dog의 메소드를 사용할 수 있게되고 setFood를 통해 변수를 바꿔주겠습니다.

dog.setDogFood("강아지 사료");


여기서 animal의 getFood를 호출하면 "강아지 사료"가 출력됩니다.

//다운캐스팅 전
animal.getFood(); //="먹이"
//다운캐스팅 후
animal.getFood(); //="강아지 사료"

상속을 방지하는 이유입니다.

2. final field

모든 필드가 final이여야 합니다.

public final class Person{
	private int age;

    public void addAge(){
    	this.age++;
    }

이런 경우 항상 값이 변조될 수 있어 불변객체가 아니게 됩니다.

3. setter 미제공

setter는 많은 지침속에서 지양하는 메소드입니다.

public final class Person{
	private int age;

    public void setAge(int age){
    	this.age = age;
    }

위 예시와 비슷한 이유 입니다.

4.필드가 가변객체를 참조 시 방어적 복사 사용

불변 객체가 가변 객체를 참조할 경우, 방어적 복사를 통해 외부에서 객체의 상태를 변경할 수 없도록 해야 합니다.

public class Home{
	String address;

    public Home(String address){
    	this.address = address;
    }
}

public final class Person{
	private final String name;
    private final Home home;

    public Person(Home home, String name){
    	this.home = home;
        this.name = name;
    }

    public Home getHome(){
        return this.home;
    }
}

이렇게 Person의 필드 중 Home 객체가 있다고 가정합니다.

Home home = new Home("대한민국");
Person person = new Person(home, "가나다");

System.out.println(person.getHome().address); //="대한민국"

person.getHome().address = "미국";

System.out.println(person.getHome().address); //="미국"

때문에 person은 불변 객체가 될 수 없게 됩니다.

그렇다면 어떻게 불변객체로 만들 수 있을까요?

방어적 복사를 이용해보겠습니다.

public final class Person{
	private final String name;
    private final Home home;

    public Person(Home home, String name){
    	this.home = new Home(home.address); // 변경점 1
        this.name = name;
    }

    public Home getHome(){
        return new Home(home.address); //변경점 2
    }
}

어떤가요? 방어적 복사를 사용한다면 새로운 Home 객체로 필드가 초기화 되고 접근 시에도 새로운 Home 객체를 만들어서 반환하기 때문에 값을 변경할 수 없게됩니다


결론

불변 객체에 대한 이해는 객체 지향 프로그래밍에서 안정적이고 유지보수가 용이한 소프트웨어를 설계하는 데 핵심적인 역할을 하기 때문에 꼭 알아야 할 개념입니다.

실제 프로그래밍에서 불변 객체를 적절하게 활용하며, 가변성이 필요한 경우에는 방어적 복사나 다른 불변성 유지 전략을 고민하게 되었습니다.

0개의 댓글