전에 객체 주입에 관련해서
•
생성자의 주입
•
setter 주입
•
필드 주입
이렇게 있다고 했는데 생성자를 통한 주입으로 진행하면 객체가 불변성을 가지고
setter 주입은 이미 객체를 생성하고 넘겨주기 때문에 객체의 불변성이 없다는 말이 있었다.
그럼 객체의 불변성이 대체 뭘까? 그리고 객체의 불변성을 왜 유지해야 좋을까?
객체의 불변성이란,
객체가 최초 생성된 시점 이후 해당 객체의 상태를 변경할 수 없는, 값이 변하지 않는다는 뜻
객체의 불변성의 중요한 이유
근데 왜 불변성이 그렇게 중요할까?
1.
예측 가능성
•
객체가 불변하면, 코드의 다른 부분에서 그 객체를 사용할 때 그 내용이 변하지 않았음을 확신할 수 있다.
•
프로그램이 어떻게 동작할지 예측하기 쉽고, 버그를 줄이고 디버깅을 더 쉽게 할 수 있다.
2.
스레드 안전성
•
멀티스레딩 환경에서, 여러 스레드가 동시에 같은 객체를 참조하고 있을 때,
객체가 불변하면 내용이 변경될 위험이 없어서 데이터가 꼬이거나 하는 문제를 걱정하지 않아도 된다. (Tread safe)
•
즉, 불변 객체는 스레드에 안전해서 복잡한 동기화 처리 없이도 안전하게 사용할 수 있다.
3.
메모리 사용 최적화
•
불변 객체는 한 번 생성되면 그 상태가 변하지 않으므로, 같은 값을 가진 객체가 필요할 때마다 새로 생성하는 대신 이미 있는 객체를 재사용할 수 있음. 이는 메모리 사용량을 줄이는 데 도움이 된다.
4.
함수형 프로그래밍
•
함수형 프로그래밍에서는 데이터의 불변성을 중요!!!.
•
데이터가 변하지 않으므로 부작용(side effects) 없이 데이터를 처리할 수 있다.
•
프로그램을 더 이해하기 쉽고 관리하기 쉬운 형태로 만들어 준다.
불변 객체를 만드는 조건
불변 클래스
불변 클래스를 만들기 위한 몇 가지 규칙이 있다.
1.
클래스를 final로 선언해서 상속을 막는다.
2.
모든 필드를 private과 final로 선언.
3.
외부에서 객체의 상태를 변경할 수 없도록, setter 메서드를 제공하지 않는다.
4.
객체의 상태를 반환하는 메서드가 있을 경우, 객체의 실제 내부 상태를 직접 노출하지 않도록 복사본을 반환.
public final class ImmutablePerson {
private final String name;
private final int age;
public ImmutablePerson(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
}
Java
복사
이 예제에서 ImmutablePerson 클래스는 불변이다.
객체가 한 번 생성되면, 그 name과 age는 변경될 수 없다.
getName()과 getAge() 메서드는 객체의 상태를 외부에 제공하지만, 이 객체의 상태를 변경할 수 있는 메서드는 제공되지 않음. 이렇게 불변 객체를 사용하면, 객체의 상태가 예기치 않게 변경되는 것을 방지할 수 있어서 프로그램의 안정성과 예측 가능성이 높아진다.
다른 예제
이 클래스는 색상을 나타내며, 한 번 생성되면 그 색상의 값(RGB 값)을 변경할 수 없다.
public final class ImmutableRGB {
// 모든 필드는 final로 선언되어야 하며, 객체 생성 후 변경할 수 없다.
private final int red;
private final int green;
private final int blue;
private final String name;
// 생성자를 통해 모든 값을 초기화. 한 번 설정된 후에는 변경할 수 없다.
public ImmutableRGB(int red, int green, int blue, String name) {
checkRange(red, green, blue);
this.red = red;
this.green = green;
this.blue = blue;
this.name = name;
}
// RGB 값이 유효한 범위 내에 있는지 확인. 유효하지 않은 경우 예외를 발생시킨다.
private void checkRange(int red, int green, int blue) {
if (red < 0 || red > 255 || green < 0 || green > 255 || blue < 0 || blue > 255) {
throw new IllegalArgumentException("Color value out of range");
}
}
// 각 필드 값을 반환하는 메서드들. 객체의 상태를 변경하는 메서드는 제공하지 않음.
public int getRed() {
return red;
}
public int getGreen() {
return green;
}
public int getBlue() {
return blue;
}
public String getName() {
return name;
}
// 새로운 색상을 만들어 반환하는 메서드. 이 클래스의 인스턴스는 불변이므로, 새 상태를 반영하기 위해 새로운 객체를 반환한다.
public ImmutableRGB invertColor() {
return new ImmutableRGB(255 - red, 255 - green, 255 - blue, "Inverted " + name);
}
}
Java
복사
상태를 변경하려는 시도가 필요한 경우, 원본 객체는 그대로 유지되고 변경된 새로운 상태를 가진 새 객체가 반환된다.
예를 들어, invertColor 메소드는 원본 색상의 반전된 색상을 가진 새 ImmutableRGB 객체를 반환.
이렇게 불변 클래스를 사용하면, 객체의 상태가 예상치 못한 시점에 변경되는 것을 방지할 수 있으며, 멀티스레드 환경에서의 안전성과 예측 가능성이 향상된다.
정리
1.
스레드 안전성
•
불변 객체는 여러 스레드에서 동시에 사용되어도 상태가 변경되지 않기 때문에, 스레드 안전성을 자연스럽게 보장.
•
따라서 복잡한 동기화 처리 없이도 안전하게 사용할 수 있다.
2.
실행 시간 버그 감소
•
객체가 변경되지 않기 때문에, 예상치 못한 상태 변화로 인한 버그 발생 가능성이 줄어든다.
•
프로그램의 예측 가능성과 안정성이 향상.
3.
코드 이해와 유지 보수성 향상
•
불변 객체를 사용하면 객체의 생명주기 동안 상태가 일정하게 유지되므로, 코드를 이해하고 디버깅하기가 더 쉬워짐.
4.
메모리 오버헤드 감소
•
불변 객체는 안전하게 공유될 수 있으므로, 동일한 데이터를 가진 여러 객체의 인스턴스를 생성할 필요가 없다.
이는 메모리 사용량을 최적화하는 데 도움이 됨.