개념
불변 클래스
인스턴스 내부 값을 수정할 수 없는 클래스
어떻게 불변 클래스를 만들까 (규칙 5가지)
1. 객체의 상태를 변경하는 메서드를 제공하지 않는다.
public class Car {
private final int position;
public Car(int position) {
this.position = position;
}
public int getPosition() {
return position;
}
public static void main(String[] args) {
// 위치를 1로 초기화한 후 변경 불가.
// 위치가 2인 차가 필요할 경우, 객체를 새로 만들어야만 한다.
Car car1 = new Car(1);
System.out.println("current position: " + car1.getPosition());
}
}
2. 클래스를 확장할수 없도록 만든다.
2.1 확장 가능
public class Human {
private int age;
private String name;
public Human(int age, String name) {
this.age = age;
this.name = name;
}
public int getAge() {
return age;
}
public String getName() {
return name;
}
}
public class Student extends Human{
private int grade;
public Student(int age, String name, int grade) {
super(age, name);
this.grade = grade;
}
public int getGrade() {
return grade;
}
}
2.2 확장 불가 (final 키워드 사용)
3. 모든 필드를 final로 선언
4. 모든 필드를 private으로 선언
- public final도 사용 가능하지만 다음 릴리즈 때 표현 변경이 불가능하므로 권장하지 않음
5. 자신 외에는 내부의 가변 컴포넌트에 접근할 수 없도록 만든다.
- 배열은 final로 선언해도, add 메서드를 통해 item을 추가할 수 있습니다.
따라서 private으로 접근 제어자를 지정하여, 클라이언트의 접근을 막아야합니다.
가변 객체 예시
* equals, hashcode, toString은 intellij의 자동완성을 사용하여, 책과 다릅니다.
예시(Complex 클래스)에서 주목할 점
- 함수형 프로그래밍으로 구현되었습니다.. (함수형프로그래밍의 정의는 코드 다음에 설명합니다.)
- 메서드명을 add 대신 plus를 사용하여, 객체의 값이 변경되지 않음을 강조하였습니다.
* 참고)
BigDecimal과 BigInteger는 add를 사용하여, 사람들이 종종 함수의 뜻을 착각한다고 합니다.
public final class Complex {
private final double re;
private final double im;
public Complex(double re, double im) {
this.re = re;
this.im = im;
}
public double realPart() { return re; }
public double imaginaryPart() { return im; }
public Complex plus(Complex c) {
return new Complex(re + c.re, im + c.im);
}
public Complex minus(Complex c) {
return new Complex(re - c.re, im - c.im);
}
public Complex times(Complex c) {
return new Complex(re * c.re - im * c.im,
re * c.im + im * c.re);
}
public Complex dividedBy(Complex c) {
double tmp = c.re * c.re + c.im * c.im;
return new Complex((re * c.re + im * c.im) / tmp,
(im * c.re - re * c.im) / tmp);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Complex complex = (Complex) o;
return Double.compare(complex.re, re) == 0 && Double.compare(complex.im, im) == 0;
}
@Override
public int hashCode() {
return Objects.hash(re, im);
}
@Override
public String toString() {
return "Complex{" +
"re=" + re +
", im=" + im +
'}';
}
}
함수형 프로그래밍 vs 명령형(=절차적) 프로그래밍
1. 함수형: 피연산자에 함수를 적용하여 결과 값을 리턴하지만, 피연산자는 그대로임
2. 명령형: 피연산자 자체의 상태가 바뀐다.
불변객체의 장점
- 객체의 변경을 신경 쓸 필요가 없기 때문에, 디버깅이 편리합니다.
- 예외가 발생하더라도 그 상태값은 변경되지 않아 안전합니다.
- Thread-safe하므로, 객체 간 안심하고 공유할 수 있습니다.
(예시. 아래 코드의 BigInteger 클래스)
signum만 반대로 만들고, mag 값은 공유 합니다.
public class BigInteger extends Number implements Comparable<BigInteger> {
/**
* 부호
*/
final int signum;
/**
* 크기(절댓값)
*/
final int[] mag;
// ... (생략) ....
/**
* 부호를 반대로한다
*/
public BigInteger negate() {
return new BigInteger(this.mag, -this.signum);
}
// ... (생략) ....
}
불변 객체를 잘 사용하는 방법
- 스레드간 안전하므로, 한번 만든 인스턴스를 최대한 재활용하는 것이 좋습니다.
- clone 메서드를 제공하지 않아야합니다.
: 복사해도 원본과 똑같으므로 복사의 의미가 없습니다.
어떻게 재할용할까
1. 자주 쓰이는 값듣을 상수로 사용할 수 있습니다. (아래 예시 참고)
public static final Complex ZERO = new Complex(0,0);
public static final Complex ONE = new Complex(1,0);
public static final Complex I = new Complex(0,1);
2. 정적 팩토리를 사용
- 메모리 사용량과 가비지 컬렉션 비용이 줄어듭니다.
불변 객체의 단점
값이 다르면 반드시 독립된 객체로 만들어야한다.
예) 로또 넘버 생성기를 만든다고 가정하자. 이 때 로또 티켓 구매 이벤트가 발생할 때마다, 각 숫자를 새로 만든다고 가정한다면 어떨까. 메모리 낭비이다. 이 경우 정적 팩터리 메서드를 사용하여 성능을 향상 시킬 수 있다.
'개발 > [스터디] 이펙티브 자바' 카테고리의 다른 글
[이펙티브자바]아이템27. 비검사 경고를 제거하라 (0) | 2022.01.02 |
---|---|
[이펙티브자바]아이템26. 로타입은 사용하지 말라 (0) | 2022.01.01 |
[책 요약]이펙티브자바 21~25 (0) | 2021.12.26 |
아이템 16. public 클래스에서는 public 필드가 아닌 접근자 메서드를 사용하여라. (0) | 2021.12.19 |
아이템 15. 클래스와 멤버의 접근권한을 최소화하여라 (0) | 2021.12.19 |