Java 불변 객체 - Immutable Object
1. 서론
자바의 객체를 불변 객체로서 보호하는 방법입니다.
2. 불변 객체란 (Immutable Object)
객체가 불변하다는 것은 생성된 이후에 변하지 않는 것을 의미합니다.
아마도 자바 언어를 처음 배울 때 쓰레드 공유를 통해서 은행에 잔고를 확인하는 코드는 익숙하실 것 같습니다. 쓰레드 동기화 기능을 사용하지 않으면, 동시에 여러 곳에서 잔고를 빼가고 잔액이 마이너스를 기록하는 것을 볼 수 있는데요.
불변 객체도 비슷합니다. 결국에는 여러 곳에서 참조를 많이 하는, 동시적으로 이벤트가 발생하는 애플리케이션에서 변하지 않는 값을 가질 때 사용될 수 있습니다.
하지만 당연히 불변 객체의 단점도 있겠죠? 생성할 때 초기값이 아닌, 새로운 값을 입력하려면 새 객체를 만들어야 합니다. 그만큼 자원의 소모가 많아질 수밖에 없습니다. 그만큼 코드 재사용성이 떨어진다고 말할 수 있습니다.
물론 메모리에서 해제가 되는 시점은 가변 객체와 마찬가지로, 더 이상 참조하지 않을 때입니다.
이러한 단점에도 불구하고, 오라클 문서에서는 그만큼 같은 객체를 잘 사용하게 된다면 GC의 overhead를 막을 수 있다고 합니다. 당연하게 한번 메모리에 할당 후 같은 객체를 계속해서 호출하더라도, 새롭게 할당할 이유가 없기 때문입니다.
또한 가변 객체의 값이 의도치 않게 변하는 것을 막는 불필요한 코드도 제거할 수 있다고 합니다.
3. 불변 객체를 정의하는 방법
오라클 문서에서는 불변 객체를 정의하는 4가지 전략을 소개하고 있습니다.
- "setter" 메서드를 사용하지 마라
- 모든 필드(field)를 final, private으로 사용하라
- 상속을 받는 subclass가 해당 메소드를 오버 라이딩하게 하지 마라. 간단한 방법은 그냥 final 클래스로 선언하라
- 만약 인스턴스 필드가 가변 객체에 포함되어 참조되고 있다면, 해당 객체가 변하게 허락하지 마라 (가변 객체에 제공하지 마라)
4. 예제
예제 코드는 아래 사이트를 참고했습니다.
howtodoinjava.com/java/basics/how-to-make-a-java-class-immutable/
test.java
필드들을 정의한 클래스입니다.
public final class Test {
/*
* String은 불변 객체 입니다.
* Integer 같은 Wrapper 클래스 또한 불변 객체 입니다.
* Setter가 있더라도 값은 변하지 않습니다.
*/
private final Integer immutableField1; // 불변 필드 1
private final String immutableField2; // 불변 필드 2
private final Date mutableField; // 가변 필드
private Test(Integer fld1, String fld2, Date date)
{
this.immutableField1 = fld1;
this.immutableField2 = fld2;
this.mutableField = new Date(date.getTime());
}
public static Test createNewInstance(Integer fld1, String fld2, Date date)
{
return new Test(fld1, fld2, date);
}
public Integer getImmutableField1() {
return immutableField1;
}
public String getImmutableField2() {
return immutableField2;
}
/*
* Date는 가변 객체이므로 값이 변합니다.
* 그래서 return 값으로 새 Date 객체를 만들어 현재의 시간을 가지고 옵니다.
*/
public Date getMutableField() {
return new Date(mutableField.getTime());
}
@Override
public String toString() {
return immutableField1 +" - "+ immutableField2 +" - "+ mutableField;
}
}
TestMain.java
Test 객체를 사용하는 클래스입니다.
public class TestMain {
public static void main(String[] args) {
/*
* 현재 생성자 초기화에 사용되는 필드들은 불변 필드
*/
Test im = Test.createNewInstance(100,"Test", new Date());
System.out.println("====================== 첫번째 초기화 ======================");
System.out.println(im);
/*
* 두번째 초기화
* 10000, "Test changed", 10 입력
*/
tryModification(im.getImmutableField1(),im.getImmutableField2(),im.getMutableField());
System.out.println("====================== 두번째 초기화 ======================");
System.out.println(im);
}
private static void tryModification(Integer immutableField1, String immutableField2, Date mutableField) {
immutableField1 = 10000;
immutableField2 = "Test changed";
mutableField.setDate(10);
}
}
결과
====================== 첫번째 초기화 ======================
100 - Test - Sat Apr 24 13:35:14 KST 2021
====================== 두번째 초기화 ======================
100 - Test - Sat Apr 24 13:35:14 KST 2021
첫 번째, 두 번째의 결과가 같습니다. 두번째 초기화에서는 다른 값을 입력했는데 불구하고요.
말 그대로 '불변(不辨)' 했습니다.
코딩을 하다 보면 같은 객체를 실수로 다시 호출해서 값이 변하게 되는 경우가 많습니다. 이때 동기화 문제가 없이, thread-safe 한 코드를 작성할 수 있습니다.
5. 참고 문헌
docs.oracle.com/javase/tutorial/essential/concurrency/immutable.html
howtodoinjava.com/java/basics/how-to-make-a-java-class-immutable/