생성 후에 내부의 값을 바꿀 수 없는 객체를 말한다.
자바에서는 대표적인 불변객체로 String을 예로 들 수 있다.
String string = "abc";
string = "def";
위의 코드는 string
이라는 변수의 값이 바뀐 것 처럼 보이지만, 실제로는 "def"라는 새로운 객체가 생성되고 string
변수가 "def"를 가리키게 한 것이다. 즉 원래 "abc"객체의 값은 여전히 "abc"로 남아있다. 더 이상 참조되지 않는 것 뿐이다.
즉 불변객체는 new 인스턴스
로 재할당하는 것 이외에는 값을 바꿀 수 없다.
원시타입을 멤버변수로 가지고 있는 객체를 불변객체화 해보자.
아래와 같이 변수에 final
을 붙여주면 Racer
는 불변객체가 된다. 외부에서 값을 바꿔줄 수 없기 때문이다.
public class Racer {
private final String name;
public Racer(String name) {
this.name = name;
}
}
만약 아래와 같은 테스트는 어떻게 될까?
class ApplicationTest {
@Test
void equals() {
Racer racer1 = new Racer("air");
assertEquals(racer1, new Racer("air"));
}
}
racer1
과 new Racer("air")
는 둘다 같은 이름을 가졌지만 테스트는 실패한다. 가지고 있는 주소값이 서로 다르기 때문이다.
따라서 불변객체를 사용할때는 거의 필수적으로 equals
와 hashcode
를 override 해줘야한다.
public class Racer {
private final String name;
public Racer(String name) {
this.name = name;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Racer racer = (Racer) o;
return Objects.equals(name, racer.name);
}
@Override
public int hashCode() {
return Objects.hash(name);
}
}
이후 다시 테스트를 진행하면 테스트가 통과한다.
만약 참조타입인 경우는 어떻게 될까?
public class Car {
private final Racer racer;
}
class Racer {
private final String name; // final이 없다면 Car는 불변객체가 될 수 없다.
public void setName(String name) {
this.name = name;
}
}
이 경우는 Racer에 만약 final
이 붙어있지 않다면 Car는 불변객체가 될 수 없다. Car 자체는 final
일지라도 Racer의 값은 setter가 있다면 변경될 수 있기 때문이다. 따라서 이런 경우 모두 final
을 붙여줘야한다.
list인 경우 아래와 같이 새로 감싸주지않는다면 주소값이 같기때문에 데이터의 변경이 반영되므로 불변객체라고 할 수 없다. 따라서 아래와 같이 new ArrayList<>()로 감싸주면 내부의 cars는 새로운 주소가 되기 때문에 외부와 연결이 끊어진다.
외부에서 유입되는 데이터 뿐만아니라 외부로 데이터를 내보낼 때도 새로 감싸서 내보낸다. getCars()의 Collections.unmodifiableList
도 새로운 주소값을 가진 List를 반환해준다.
public class Cars {
private final List<Car> cars;
public Cars(List<Car> cars) {
this.cars = new ArrayList<>(cars);
}
public List<Car> getCars() {
return Collections.unmodifiableList(cars);
}
}
이런 불변객체를 사용하는 이유는 객체에 대한 신뢰성이 높아진다. 한 번 생성되고 값이 변하지 않기 때문이다.
하지만 매번 새로운 인스턴스를 생성해야해서 성능이 떨어질 수 있다고 생각할 수 있다. 그러나 불변객체를 사용함으로서 얻는 이점이 메모리 걱정보다 더 크다고 오라클문서에 나와있다고 한다.(메모리는 GC에 맡긴다..)