tonykang22 / study

0 stars 0 forks source link

[이펙티브 자바] 아이템 17. 변경 가능성을 최소화 하라. #128

Open tonykang22 opened 1 year ago

tonykang22 commented 1 year ago

아이템 17. 변경 가능성을 최소화 하라.

핵심 정리 1: 불변 클래스


예시

예시에 활용될 PhoneNumber 클래스

@Getter @Setter
public class PhoneNumber {

    private short areaCode;

    private short prefix;

    private short lineNum;

}


객체의 상태를 변경하는 메서드를 제외

@Getter
public class PhoneNumber {

    public PhoneNumber(short areaCode, short prefix, short lineNum) {
        this.areaCode = areaCode;
        this.prefix = prefix;
        this.lineNum = lineNum;
    }

    private short areaCode;

    private short prefix;

    private short lineNum;

}



MyPhoneNumber는 PhoneNumber 타입으로 사용한다면 불변이 아니게 된다.


public class MyPhoneNumber extends PhoneNumber {

    public MyPhoneNumber(short areaCode, short prefix, short lineNum) {
        super(areaCode, prefix, lineNum);
    }

    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}



클래스에 final 키워드로 상속 금지

모든 필드에 final 키워드로 필드 변경을 방지

public final class PhoneNumber {

    private final 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;
    }

    // 내부에서 사용 중 필드 값이 언제 변경될지 모르기 때문이다.
//    public short doSomething() {
//        return areaCode + 1;
//    }
}



"자신 외에는 내부의 가변 컴포넌트에 접근할 수 없도록 한다."

public class Address {

    private String zipCode;

    private String street;

    private String city;

}
public final class Person {

    private final Address address;

    public Person(Address address) {
        this.address = address;
    }

    // 이와 같은 방법은 가변적인 컴포넌트를 제공하기에 불변 클래스로서 옳지 않은 방법이다.
//    public Address getAddress() {
//        return address;
//    }

    // 방어적인 복사 방법으로 전달하는 방법
    public Address getAddress() {
        Address copyOfAddress = new Address();
        copyOfAddress.setStreet(address.getStreet());
        copyOfAddress.setZipCode(address.getZipCode());
        copyOfAddress.setCity(address.getCity());
        return copyOfAddress;
    }

    public static void main(String[] args) {
        Address seattle = new Address();
        seattle.setCity("Seattle");

        Person person = new Person(seattle);

        Address redmond = person.getAddress();
        redmond.setCity("Redmond");

        // Seattle
        System.out.println(person.address.getCity());
    }
}



tonykang22 commented 1 year ago

핵심 정리 2: 불변 클래스의 장점과 단점

// 코드 17-1 불변 복소수 클래스 (106-107쪽)
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);
    }

    @Override public boolean equals(Object o) {
        if (o == this)
            return true;
        if (!(o instanceof Complex))
            return false;
        Complex c = (Complex) o;

        // == 대신 compare를 사용하는 이유는 63쪽을 확인하라.
        return Double.compare(c.re, re) == 0
                && Double.compare(c.im, im) == 0;
    }
    @Override public int hashCode() {
        return 31 * Double.hashCode(re) + Double.hashCode(im);
    }

    @Override public String toString() {
        return "(" + re + " + " + im + "i)";
    }
}



불변 객체끼리는 내부 데이터를 공유할 수 있다.

image

image

public class BigIntExample {

    public static void main(String[] args) {
        BigInteger ten = BigInteger.TEN;
        BigInteger minusTen = ten.negate();

        // Point 가 불변이라서 가능한 케이스이다.
        // 이런 이점이 있기에 불변 객체들이 많을 수록 이점이 많아진다.
        final Set<Point> points = new HashSet<>();
        Point firstPoint = new Point(1, 2);
        points.add(firstPoint);

    }
}
tonykang22 commented 1 year ago

핵심 정리 3: 불변 클래스를 만들 때 고려할 것



정적 팩터리 사용

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);
        }
    }

    // 코드 17-2 정적 팩터리(private 생성자와 함께 사용해야 한다.) (110-111쪽)
    public static Complex valueOf(double re, double im) {
        return new MyComplex(re, im);
    }

...
}


public class ComplexExample {

    // 외부에서 정적 팩터리를 통해 인스턴스를 만들면 MyComplex가 아닌 Complex
    public static void main(String[] args) {
        Complex complex = Complex.valueOf(1, 0.222);
    }
}



상속이 허용된 불변 클래스라면

public class BigIntegerUtils {

    public static BigInteger safeInstance(BigInteger val) {
        return val.getClass() == BigInteger.class ? val : new BigInteger(val.toByteArray());
    }
}



모든 "외부에 공개하는" 필드가 final이어야 한다.

public final class PhoneNumber {

...

    // 해시코드를 지연 초기화하는 hashCode 메서드 - 스레드 안정성까지 고려해야 한다. (71쪽)
    private volatile int hashCode; // 자동으로 0으로 초기화된다.

    @Override public int hashCode() {
        if (this.hashCode != 0) {
            return hashCode;
        }

        synchronized (this) {
            int result = hashCode;
            if (result == 0) {
                result = Short.hashCode(areaCode);
                result = 31 * result + Short.hashCode(prefix);
                result = 31 * result + Short.hashCode(lineNum);
                this.hashCode = result;
            }
            return result;
        }
    }

}
leeyuunsung commented 1 year ago

완벽 공략

leeyuunsung commented 1 year ago

완벽 공략 32. final과 자바 메모리 모델(JMM)

final을 사용하면 안전하게 초기화 할 수 있다. "안전하다" 란?

JMM 스펙상 가변 클래스의 불완전한 초기화 예시

public class Whiteship {
    private int x;
    private int y;
    public Whiteship() {
        this.x = 1;
        this.y = 2;
    }
    public static void main(String[] args) {
        Whiteship whiteship = new Whiteship();

        /* 실행 순서는 메모리 모델만 준수한다면 변경될 수 있다
         * 단, 한 스레드 내에서 유효한지만 확인한다
         * 따라서 멀티스레드 환경에서는 불완전한 초기화가 발생할 수 있다
         *
         * Object w = new Whiteship()
         * whiteship = w
         * w.x = 1
         * w.y = 2
         *
         * or
         * Object w = new Whiteship()
         * w.x = 1
         * w.y = 2
         * whiteship = w
         * */
    }
}

요약

leeyuunsung commented 1 year ago

완벽 공략 33. java.util.concurrnet 패키지

병행(concurrency) 프로그래밍에 유용하게 사용할 수 있는 유틸리티 묶음

병행(Concurrency) vs 병렬(Parallelism)

image

완벽 공략 33. CountDownLatch

다른 여러 스레드로 실행하는 여러 오퍼레이션이 마칠 때까지 기다릴 때 사용할 수 있는 유틸리티

CountDownLatch 예시

public class ConcurrentExample {
    public static void main(String[] args) throws InterruptedException {
        int N = 10;
        CountDownLatch startSignal = new CountDownLatch(1);
        CountDownLatch doneSignal = new CountDownLatch(N);

        for (int i = 0; i < N; ++i) // create and start threads
            new Thread(new Worker(startSignal, doneSignal)).start();

        ready();                        // don't let run yet
        startSignal.countDown();      // let all threads proceed
        doneSignal.await();           // wait for all to finish
        done();
    }

    private static void ready() {
        System.out.println("준비~~~");
    }

    private static void done() {
        System.out.println("끝!");
    }

    private static class Worker implements Runnable {

        private final CountDownLatch startSignal;
        private final CountDownLatch doneSignal;

        public Worker(CountDownLatch startSignal, CountDownLatch doneSignal) {
            this.startSignal = startSignal;
            this.doneSignal = doneSignal;
        }

        public void run() {
            try {
                startSignal.await();
                doWork();
                doneSignal.countDown();
            } catch (InterruptedException ex) {
            } // return;
        }

        void doWork() {
            System.out.println("working thread: " + Thread.currentThread().getName());
        }
    }
}