public class PhoneNumber {
private short areaCode, prefix, lineNum;
public PhoneNumber(short areaCode, short prefix, short lineNum) {
this.areaCode = areaCode;
this.prefix = prefix;
this.lineNum = lineNum;
}
public short getAreaCode() {
return areaCode;
}
public short getPrefix() {
return prefix;
}
public short getLineNum() {
return lineNum;
}
}
생성자로 초기값을 설정하고 setter와 같은 객체상태를 변경할 수 있는 메서드를 제공하지 않습니다.
첫번째 규칙으로 객체 상태를 변경할 수는 없지만 만약 PhoneNumber클래스를 상속받는 객체의 상태값을 변경하는 메소드가 제공됐을때는 불변클래스가 아니게 됩니다. 따라서 클래스를 final로 선언하거나 생성자를 private으로 생성하여 상속을 받을 수 없게 합니다.
public final class PhoneNumber {
private short areaCode, prefix, lineNum;
public PhoneNumber(short areaCode, short prefix, short lineNum) {
this.areaCode = areaCode;
this.prefix = prefix;
this.lineNum = lineNum;
}
// getter
}
public class PhoneNumber {
private short areaCode, prefix, lineNum;
private PhoneNumber(short areaCode, short prefix, short lineNum) {
this.areaCode = areaCode;
this.prefix = prefix;
this.lineNum = lineNum;
}
// getter
}
private생성자로 선언한 경우는 초기값을 설정할 수 있는 정적팩토리 메서드를 제공해야합니다.
public class PhoneNumber {
private final short areaCode, prefix, lineNum; // final로 선언
public PhoneNumber(short areaCode, short prefix, short lineNum) {
this.areaCode = areaCode;
this.prefix = prefix;
this.lineNum = lineNum;
}
public void doSomething() { // 이렇게 내부 상태를 변경하는 경우를 방지
this.areaCode = 10;
}
// getter
}
필드를 final로 선언하면 doSomething메서드와 같이 내부 필드상태를 변경할 경우 컴파일에러가 발생하기 때문에 컴파일시 상태변경을 확인할 수 있어 객체 상태를 변경하는 실수를 방지할 수 있습니다.
public class PhoneNumber {
public final short areaCode, prefix, lineNum; // 외부에 노출
public PhoneNumber(short areaCode, short prefix, short lineNum) {
this.areaCode = areaCode;
this.prefix = prefix;
this.lineNum = lineNum;
}
// getter
}
외부에서 PhoneNumber의 필드들을 노출하기때문에 모든 필드들을 private으로 선언합니다.
public class Address {
private String zipCode;
private String street;
private String city;
public String getZipCode() {
return zipCode;
}
public void setZipCode(String zipCode) {
this.zipCode = zipCode;
}
public String getStreet() {
return street;
}
public void setStreet(String street) {
this.street = street;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
}
setter를 통해서 Address의 상태를 변경할 수 있는 가변 클래스입니다.
public final class Person {
private final Address address;
public Person(Address address) {
this.address = address;
}
public Address getAddress() {
return address;
}
}
Person클래스는 불변클래스인것 처럼 보이지만 Address클래스를 참조하는데 이 Address클래스가 가변클래스이기 때문에 Person객체가 참조하는 Address객체의 정보가 얼마든지 빠뀔수 있습니다.
Address seattle = new Address();
seattle.setCity("Seattle");
Person person = new Person(seattle);
Address redmond = person.getAddress();
redmond.setCity("Redmond");
public final class Person {
private final Address address;
public Person(Address address) {
this.address = address;
}
public Address getAddress() { // 가변 객체에 접근할 수 없게 제거
return address;
}
}
결국 Person이 가지고 있는 정보가 변경되기때문에 불변성이 깨지게 됩니다. 따라서 가변적인 객체를 접근할 수 없게 막아야합니다.
public final class Person {
private final Address address;
public Person(Address address) {
this.address = address;
}
public Address getAddress() {
Address copyOfAddress = new Address();
copyOfAddress.setStreet(address.getStreet());
copyOfAddress.setZipCode(address.getZipCode());
copyOfAddress.setCity(address.getCity());
return copyOfAddress;
}
}
만약 Person객체의 Address의 정보를 제공해야한다면 Person객체가 참조하고 있는 가변객체를 직접 리턴하지 말고 새 인스턴스에 방어적복사로 리턴하도록 하면 Person객체가 참조하고 있는 Address객체의 정보를 변경할 수 없습니다.
public final class Complex {
private final double re;
private final double im;
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);
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);
}
// 코드 17-2 정적 팩터리(private 생성자와 함께 사용해야 한다.) (110-111쪽)
public static Complex valueOf(double re, double im) {
return new Complex(re, 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);
}
}
public Complex plus(Complex c) {
return new Complex(re + c.re, im + c.im);
}
Complex클래스의 덧셈연산의 경우 현재 Complex의 값이 바뀌는게 아니라 새로운 Complex객체가 생성되도록 하여 값이 바뀌지가 않습니다.
package me.whiteship.chapter04.item17.part3;
// 코드 17-1 불변 복소수 클래스 (106-107쪽)
public class Complex {
private final double re;
private final double im;
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);
private Complex(double re, double im) {
this.re = re;
this.im = im;
}
private static class MyComplex extends Complex {
private MyComplex(double re, double im) {
super(re, 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);
}
// 코드 17-2 정적 팩터리(private 생성자와 함께 사용해야 한다.) (110-111쪽)
public static Complex valueOf(double re, double im) {
return new MyComplex(re, 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);
}
}
private Complex(double re, double im) {
this.re = re;
this.im = im;
}
// 코드 17-2 정적 팩터리(private 생성자와 함께 사용해야 한다.) (110-111쪽)
public static Complex valueOf(double re, double im) {
return new Complex(re, im);
}