불변 객체를 알아보기 전에 불변 객체가 왜 필요한지 간단하게 알아보자.
//기본형
int a = 10;
int b = a;
System.out.println("a = " + a); // a = 10
System.out.println("b = " + b); // b = 10
b = 20;
System.out.println("a = " + a); // a = 10
System.out.println("b = " + b); // b = 10
//참조형
Address a = new Adderss("서울");
Address b = a;
System.out.println("a = " + a); // a = "서울"
System.out.println("b = " + b); // b = "서울"
b.setValue("대구");
System.out.println("a = " + a); // a = "대구" , 사이드 이펙트 발생!
System.out.println("b = " + b); // b = "대구"
위 코드를 보면 알 수 있듯이
기본형의 경우에는 a의 값(10) 을 복사해서 b에 대입하기 때문에 b를 변경할 경우 b의 값만 변경되지만
참조형의 경우에는 a의 참조값을 복사해서 b에 대입하기 때문에 b를 변경해도 a가 변경된다.
이를 해결하기 위해서 아래와 같이 a와 b를 작성할 수 있다.
Address a = new Adderss("서울");
Address b = new Adderss("서울");
b.setValue("대구")
이렇게 할 경우 내가 원하는대로 b의 값만 대구로 변경
된다. 하지만 이렇게 작성을 하더라도 하나의 객체를 공유하는 것을 막을 방법이 없다.
즉 내가 원하는 코드 작성 방식은 객체를 공유하지 않도록
Address a = new Adderss("서울");
Address b = new Adderss("서울");
이렇게 각각 a,b 를 만드는 것이지만 누군가는
Address a = new Address("서울");
Address b = a;
이렇게 작성을 할 수도 있는 것이다. 물론 a,b 모두 서울
이라는 주소를 가져야 한다면 b = a
코드가 더 효율적이긴 하다.
사실 이렇게 공유를 하는것 자체는 사이드 이펙트가 발생하지 않는다. 문제는 이렇게 공유한 객체의 값을 변경
하는 것이다.
b.setValue("대구"); // a를 공유한 b가 set을 사용해서 값을 변경하는게 문제다!
객체의 공유 참조는 막을 수 없다는 것을 위의 예시를 통해 알았다. 그래서 객체의 값을 변경할 수 없도록 불변 객체를 만들어서 사용하면 사이드 이펙트를 원천 차단할 수 있다.
// 불변 객체 생성
public class Address {
private final String value; // final 을 사용
public Address(String value) {
this.value = value;
}
public String getValue() {
return value;
}
//setter 가 없음!
@Override
public String toString() {
return "Address{" +
"value='" + value + '\'' +
'}';
}
}
위 클래스를 보면 value 필드를 final
로 선언했고 setValue()
메서드도 존재하지 않는다.
이제 value 값을 변경할 수 있는 방법이 존재하지 않는다는 것.
Address a = new Address("서울");
Address b = a; // 참조값 대입을 막을 수 있는 방법은 없다.
System.out.println("a = " + a); // a = Address{value='서울'}
System.out.println("b = " + b); // b = Address{value='서울'}
//b.setValue("대구"); //불변 객체에는 setter 메서드가 없다.
b = new Address("부산"); //새로운 인스턴스 생성
System.out.println("a = " + a); // a = Address{value='서울'}
System.out.println("b = " + b); // b = Address{value='부산'}
b의 값을 변경하기 위해 setValue()
를 사용하려 했으나 Address 클래스에는 해당 메서드가 존재하지 않는다. 어쩔 수 없이 new Address("부산")
새로운 인스턴스를 생성할 수 밖에 없다.
또 Java14 에서 등장한 record 를 사용해서 쉽게 불변 객체를 만들수도 있다.
public record class Address(String value) {}
위의 Address 코드를 record 클래스로 바꾼 것이다.
해당 클래스의 모든 필드가 final
로 선언되어 있고 각각 필드의 getter
를 가지고 있다면 record 클래스로 변경할 수 있다.
또한 record는
생성자, getter, hashCode(), equals() ,toString() 메서드를 기본으로 제공한다.
그리고 getter의 경우에는 getValue()
와 같은 형식이 아닌 value()
로 사용한다.
Address a = new Address("서울");
//a.getValue();
a.value();
간단하게 record 를 알아봤는데 더 자세히 알고 싶다면 미스터포포님의 블로그를 읽어보면 좋을 것 같다.