Dev Book Review/Effective Java

[Effective Java] Item20. 추상 클래스 보다는 인터페이스를 우선하라

자바의 다중 구현 메커니즘 : 둘다 인스턴스 메서드를 구현 형태로 제공할 수 있다 (default method) 인터페이스 : 다중 상속, 같은 타입 취급 추상클래스 : 단일 상속, 하위 클래스 (상하 관계) 1. 인터페이스의 장점 a. 기존 클래스에도 손쉽게 새로운 인터페이스를 구현해 넣을 수 있다. 인터페이스 : 요구하는 메서드를 추가하고, 클래스 선언에 implements 구문만 추가하면 끝이다. 추상클래스 : 계층 구조상 확장시킨 클래스의 공통 조상이 되어, 클래스 계층 구조를 생각해야한다. b. 믹스인 정의에 안성맞춤이다. 믹스인 : 클래스가 구현할 수 있는 타입. 믹스인을 구현한 클래스에 원래의 '주된 타입'외에도 선택적 기능을 '혼합'하여 사용한다. 추상클래스 : 기존 클래스에 덧씌울 수 없..

[Effective Java] Item20. 추상 클래스 보다는 인터페이스를 우선하라

728x90

자바의 다중 구현 메커니즘 : 둘다 인스턴스 메서드를 구현 형태로 제공할 수 있다 (default method)

  • 인터페이스 : 다중 상속, 같은 타입 취급
  • 추상클래스 : 단일 상속, 하위 클래스 (상하 관계)

 

1. 인터페이스의 장점

a. 기존 클래스에도 손쉽게 새로운 인터페이스를 구현해 넣을 수 있다.

  • 인터페이스 : 요구하는 메서드를 추가하고, 클래스 선언에 implements 구문만 추가하면 끝이다.
  • 추상클래스 : 계층 구조상 확장시킨 클래스의 공통 조상이 되어, 클래스 계층 구조를 생각해야한다.
 

b. 믹스인 정의에 안성맞춤이다.

믹스인 : 클래스가 구현할 수 있는 타입.
믹스인을 구현한 클래스에 원래의 '주된 타입'외에도 선택적 기능을 '혼합'하여 사용한다.

추상클래스 : 기존 클래스에 덧씌울 수 없다. 클래스가 두 부모를 섬길 수 없는 단일 상속의 특징이 있다.

Comparable, Clonable, Serializable

 

c. 인터페이스로는 계층 구조가 없는 타입 프레임 워크를 만들 수 있다.

public interface SingerSongWriter extends Singer, SongWriter {
    void strum();
    void actSensitive();
}
public abstract class Singer {
    abstract void sing(String s);
}

public abstract class SongWriter {
    abstract void compose(int chartPosition);
}

public abstract class SingerSongWriter {
    abstract void strum();
    abstract void actSensitive();
    abstract void Compose(int chartPosition);
    abstract void sing(String s);
}

추상 클래스로 만들면 다중상속이 불가능해, 새로운 추상클래스를 만들어서 클래스 계층을 표현할 수 밖에 없다.

따라서 이 계층구조를 만들기 위해 많은 조합이 필요하고, 결국엔 고도비만 계층 구조가 만들어진다. (조합 폭발)

 

d. 래퍼 클래스 관용구와 함께 사용하면 인터페이스는 기능을 향상시키는 안전하고 강력한 수단이된다.

타입을 추상클래스로 정의했을 때 : 기능 추가 방법은 상속뿐이다. -> 활용도가 떨어지고 쉽게 깨진다

래퍼클래스의 활용도가 더 높다 : 아이템 18

 

2. 인터페이스의 디폴트 메서드 제약

  • 디폴트 메서드를 제공할 때는 @implSpec 을 붙여 문서화한다.
  • equals와 hashCode는 디폴트 메서드로 정의하면 안된다.
  • 인터페이스는 인스턴스 필드를 가질 수 없다.
  • public이 아닌 정적 멤버도 가질 수 없다.
  • 우리가 만들지 않은 인터페이스에는 디폴트 메서드를 추가할 수 없다.

 

3. 인터페이스와 추상골격 구현 클래스

a. 개념

인터페이스 : 타입 + 디폴트 메서드

골격구현 클래스 : 나머지 메서드들까지 구현

인터페이스 구현에 필요한 대부분의 일이 완료된다 -> 템플릿 메서드 패턴

네이밍 관례 : Abstract[Interface명] : AbstractCollection, AbstractSet, AbstractList, AbstractMap

 

b. 장점

추상 클래스처럼 구현을 도와주는 동시에, 추상클래스로 타입을 정의할 때 따라오는 심각한 제약에서 자유롭다.

 

4. 시뮬레이트한 다중 상속(simulated multiple inheritance)

골격 구현 클래스를 우회적으로 이용하는 방식

인터페이스를 구현한 클래스에서, 골격 구현을 확장한 private 내부 클래스를 정의하고, 각 메서드 호출을 내부 클래스의 인스턴스에 전달한다.

public interface Vending {
    void start();
    void chooseProduct();
    void stop();
    void process();
}

public abstract class AbstractVending implements Vending {
    @Override
    public void start() {
        System.out.println("vending start");
    }

    @Override
    public void stop() {
        System.out.println("stop vending");
    }

    @Override
    public void process() {
        start();
        chooseProduct();
        stop();
    }
}

public class VendingManufacturer {
    public void printManufacturerName() {
        System.out.println("Made By JavaBom");
    }
}

public class SnackVending extends VendingManufacturer implements Vending {
    InnerAbstractVending innerAbstractVending = new InnerAbstractVending();

    @Override
    public void start() {
        innerAbstractVending.start();
    }

    @Override
    public void chooseProduct() {
        innerAbstractVending.chooseProduct();
    }

    @Override
    public void stop() {
        innerAbstractVending.stop();
    }

    @Override
    public void process() {
        printManufacturerName();
        innerAbstractVending.process();
    }

    private class InnerAbstractVending extends AbstractVending {

        @Override
        public void chooseProduct() {
            System.out.println("choose product");
            System.out.println("chocolate");
            System.out.println("cracker");
        }
    }
}

 

5. 골격 구현 작성 방법

a. 인터페이스를 잘 살펴 다른 메서드들의 구현에 사용되는 기반 메서드를 선정

b. 기반 메서드들을 사용해 직접 구현할 수 있는 메서드들을 모두 디폴트 메서드로 제공

c. 기반 메서드나 디폴트 메서드로 만들지 못한 메서드가 남아있다면, 이 인터페이스를 구현하는 골격 구현 클래스를 하나 만들어서 작성한다.

d. 골격 구현은 기본적으로 상속이므로, 설계 및 문서화 지침을 모두 따라야한다.

 

6. 단순 구현

  • 골격 구현의 작은 변종
  • 골격 구현처럼 상속을 위해 인터페이스를 구현했으나 추상클래스가 아니다.
  • AbstractMap.SimpleEntry

댓글

Comments

Dev Book Review/Effective Java

[Effective Java] item 19. 상속을 고려해 설계하고 문서화하라. 그렇지 않았다면 상속을 금지하라

1. 재정의 메서드의 문서화 상속용 클래스는 재정의할 수 있는 메서드들을 내부적으로 어떻게 이용하는지(자기사용) 문서로 남겨야한다. 재정의 가능 메서드 : 호출 메서드의 API 설명에 기술, 호출 순서, 각각의 호출 결과 처리 영향 기술 호출할 수 있는 모든 상황을 남긴다. 백그라운드 스레드, 정적 초기화 과정에서 호출이 일어날 수 있는 상황 등 API 문서의 Implementation Requirements : 메서드의 내부 동작 방식 설명 메서드 주석에 @ImplSpec 태그를 붙여주면 자바독 도구가 생성된다. 좋은 API문서란 '어떻게'란 '무엇'을 하는지를 설명해야한다. 대치된다 : 그러나 상속이 캡슐화를 해치기 때문에 어쩔수 없다 - 클래스를 안전하게 상속할 수 있게 해야하기 때문 2. 상속 설..

[Effective Java] item 19. 상속을 고려해 설계하고 문서화하라. 그렇지 않았다면 상속을 금지하라

728x90

1. 재정의 메서드의 문서화

상속용 클래스는 재정의할 수 있는 메서드들을 내부적으로 어떻게 이용하는지(자기사용) 문서로 남겨야한다.

  • 재정의 가능 메서드 : 호출 메서드의 API 설명에 기술, 호출 순서, 각각의 호출 결과 처리 영향 기술
  • 호출할 수 있는 모든 상황을 남긴다.
    백그라운드 스레드, 정적 초기화 과정에서 호출이 일어날 수 있는 상황 등

API 문서의 Implementation Requirements : 메서드의 내부 동작 방식 설명

  • 메서드 주석에 @ImplSpec 태그를 붙여주면 자바독 도구가 생성된다.

좋은 API문서란 '어떻게'란 '무엇'을 하는지를 설명해야한다.

  • 대치된다 : 그러나 상속이 캡슐화를 해치기 때문에 어쩔수 없다 - 클래스를 안전하게 상속할 수 있게 해야하기 때문

 

2. 상속 설계 : 내부 동작과정 훅 선별

클래스의 내부 동작과정 중간에 끼어들 수 있는 훅(hook)을 잘 선별하여 protected 메서드 형태로 공개해야 할 수도 있다.

ex ) Clear 메서드를 호출할 때 성능을 위해 사용되는 protected removeRange() 메서드

실제 하위 클래스를 만들어 시험해본 뒤, protected 메서드의 내부 노출 정도를 결정한다.
너무 적게 노출해서 상속으로 얻는 이점마저 없애지 않게 주의한다.

상속용 클래스의 시험 방법 : 직접 하위클래스를 만들어본다.

하위클래스 여러개 만들때까지 전혀 쓰지않는 protected 멤버는 private으로 돌린다.

배포전에 반드시 클래스를 만들어 검증하자.

 

3. 생성자에서 재정의 가능 메서드 호출 금지

상속용 클래스의 생성자는 직접이든 간접이든 재정의 가능 메서드를 호출해서는 안된다.

  • 상위 클래스의 생성자가 하위 클래스의 생성자보다 먼저 실행되므로
  • 재정의한 메서드가 하위 클래스의 생성자에서 초기화한 값에 의존하면 의도대로 동작하지 않는다.
  • private, final, static 메서드는 재정의가 불가능하니 생성자에서 안심하고 호출해도 된다.

 

4. Cloneable, Serializable 인터페이스는 상속에서 닫아두자

확장하려는 프로그래머에게 엄청난 부담을 준다.

clone과 readObject 메서드는 생성자와 비슷한 효과를 낸다 (새로운 객체를 만든다.)
따라서 상속용 클래스에서 구현할 때 제약도 생성자와 비슷하다.

clone과 readObject 모두 직접이든 간접이든 재정의 가능 메서드를 호출해서는 안된다.

  • readObject : 하위 클래스가 미처 역직렬화 되기 전에 재정의한 메서드부터 호출한다.
  • clone : 하위 클래스의 clone 메서드가 복제본의 상태를 수정하기 전에 재정의한 메서드를 호출 한다.
    원본 객체에도 피해를 줄 수 있다. (clone이 완벽하지 못해 원본 객체의 참조가 있을 경우)

 

5. Serializable을 구현한 상속용 클래스 주의

readResolve나 writeReplace 메서드를 갖는다면 이 메서드는 private이 아닌 protected로 선언해야한다.

private으로 선언하면 하위 클래스에서 무시된다 : 상속 허용을 위해 내부 구현을 클래스 API로 공개

 

6. 상속에 대한 조언

a. 상속용으로 설계하지 않은 클래스는 상속을 금지한다.

  • 클래스를 final로 선언하자.
  • 모든 생성자를 private이나 package-private으로 선언하고 public 정적 팩터리를 만들어준다.

b. 구현상속보단 인터페이스 상속을 사용하는 것이 더 나은 내안이다.

c. 상속을 허용해야겠다면 재정의 가능 메서드를 사용하지 않게 만들고 문서로 남긴다.

d. 클래스 동작 유지 + 재정의 가능 메서드 사용 코드 제거

  • 재정의 가능 메서드는 자신의 본문 코드를 private 도우미 메서드로 옮긴다.
  • 그리고 이 도우미 메서드를 호출한다.
public add(){
  add();
}
private add(){
  ...
}

댓글

Comments

Dev Book Review/Effective Java

[Effective Java] Item18. 상속보다는 컴포지션을 사용하라

상속이 안전 할 때 상위 클래스와 하위클래스를 모두 같은 프로그래머가 통제하는 패키지 안에서 사용한다. 확장할 목적으로 설계되었고 문서화도 잘되었다. 상속이 안전하지 않을때 다른 패키지의 구체 클래스를 상속하는 것은 위험하다. 1. 하위 클래스가 깨지기 쉬운 이유 상속은 캡슐화를 깨뜨린다. 상위클래스의 구현에 따라 하위 클래스의 동작에 이상이 생길 수 있다. 상위클래스는 릴리스마다 내부 구현이 달라질 수 있으며, 그 여파로 하위 클래스가 오동작할 수 있다. a. 자신이 다른 부분을 사용하는 자가사용 여부 public class InstrumentedHashSet extends HashSet{ private int addCount = 0; @Overrid public boolean add(E e){ add..

[Effective Java] Item18. 상속보다는 컴포지션을 사용하라

728x90

상속이 안전 할 때

  • 상위 클래스와 하위클래스를 모두 같은 프로그래머가 통제하는 패키지 안에서 사용한다.
  • 확장할 목적으로 설계되었고 문서화도 잘되었다.

상속이 안전하지 않을때

  • 다른 패키지의 구체 클래스를 상속하는 것은 위험하다.

 

1. 하위 클래스가 깨지기 쉬운 이유

상속은 캡슐화를 깨뜨린다.

상위클래스의 구현에 따라 하위 클래스의 동작에 이상이 생길 수 있다.

상위클래스는 릴리스마다 내부 구현이 달라질 수 있으며, 그 여파로 하위 클래스가 오동작할 수 있다.

 

a. 자신이 다른 부분을 사용하는 자가사용 여부

public class InstrumentedHashSet<E> extends HashSet<E>{
  private int addCount = 0;

  @Overrid public boolean add(E e){
    addCount++;
    return super.add(e);
  }

  @Overrid public boolean addAll(Collection<? extends E> c){
    addCount += c.size();
    return super.add(c);
  }
}

잘 작동하지 않는다 : addAll 메서드가 add를 사용해 구현되어 addCount 값이 중복해서 더해진다.

내부 구현 방식에 해당하는 부분을 고려해서 수정을 해도, 이 내부구현이 다음 릴리즈에서 유지될지 알 수 없다.

 

b. 상위클래스의 메서드 동작을 재구현

addAll 메서드를 다른식으로 재정의한다 : 주어진 컬렉션을 순회하며 원소 하나당 add 메서드를 한번만 호출

시간도 더들고, 오류를 내거나 성능저하가 있을 수 있다. 하위클래스에서 접근이 불가한 private이라면 이 방식 구현 자체가 불가능하다.

 

c. 다음 릴리즈에서 상위 클래스에 새로운 메서드 추가

상위클래스에 또다른 원소 추가 메서드가 생성된다 가정하자. 하위 클래스에서 재정의하지 못한 새로운 메서드를 사용해 허용되지 않은 원소를 추가할 수 있게된다.

 

d. 클래스 확장하더라도 메서드 재정의 대신 새로운 메서드 추가

운이 없게도, 다음 릴리즈에서 상위클래스에 추가된 새 메서드가 내가 하위클래스에 추가한 메서드와 시그니처가 같고 반환타입이 다르면 컴파일조차 안된다.

 

2. 상속의 문제를 해결하는 방법 : 컴포지션

컴포지션 설계 : 기존 클래스가 새로운 클래스의 구성요소로 쓰인다.
기존 클래스 확장 대신 새로운 클래스를 만들고 private 필드로 기존 클래스의 인스턴스를 참조하자

전달 : 새 클래스의 인스턴스 메서드는 기존 클래스에 대응하는 메서드들을 호출해 그 결과를 반환한다.

전달 메서드 : 새 클래스의 메서드

// 코드 18-3 재사용할 수 있는 전달 클래스 (118쪽)
public class ForwardingSet<E> implements Set<E> {
    private final Set<E> s;
    public ForwardingSet(Set<E> s) { this.s = s; }

    public void clear()               { s.clear();            }
    public boolean contains(Object o) { return s.contains(o); }
    public boolean isEmpty()          { return s.isEmpty();   }
    public int size()                 { return s.size();      }
    public Iterator<E> iterator()     { return s.iterator();  }
    public boolean add(E e)           { return s.add(e);      }
    public boolean remove(Object o)   { return s.remove(o);   }
    public boolean containsAll(Collection<?> c)
                                   { return s.containsAll(c); }
    public boolean addAll(Collection<? extends E> c)
                                   { return s.addAll(c);      }
    public boolean removeAll(Collection<?> c)
                                   { return s.removeAll(c);   }
    public boolean retainAll(Collection<?> c)
                                   { return s.retainAll(c);   }
    public Object[] toArray()          { return s.toArray();  }
    public <T> T[] toArray(T[] a)      { return s.toArray(a); }
    @Override public boolean equals(Object o)
                                       { return s.equals(o);  }
    @Override public int hashCode()    { return s.hashCode(); }
    @Override public String toString() { return s.toString(); }
}
public class InstrumentedSet<E> extends ForwardingSet<E> {
    private int addCount = 0;

    public InstrumentedSet(Set<E> s) {
        super(s);
    }

    @Override public boolean add(E e) {
        addCount++;
        return super.add(e);
    }
    @Override public boolean addAll(Collection<? extends E> c) {
        addCount += c.size();
        return super.addAll(c);
    }
    public int getAddCount() {
        return addCount;
    }

    public static void main(String[] args) {
        InstrumentedSet<String> s = new InstrumentedSet<>(new HashSet<>());
        s.addAll(List.of("틱", "탁탁", "펑"));
        System.out.println(s.getAddCount());
    }
}

래퍼클래스 : 다른 인스턴스를 감싸고 있다 InstrumentedSet : 데코레이터 패턴

위임(deleagtion) : 컴포지션과, 전달의 조합 : 래퍼 객체가 내부 객체에 자기 자신의 참조를 넘기는 경우

 

3. 래퍼클래스의 단점

콜백(callback) 프레임워크와는 어울리지 않는다.

SELF 문제 : 콜백에서는 자기 자신의 참조를 다른 객체에게 넘겨서 다음 호출때 사용한다. 내부 객체는 자신을 감싸는 래퍼의 존재를 모르니 대신 자신(this) 참조를 넘기고, 콜백때 래퍼 객체가 아닌 내부 객체를 호출한다.

전달 메서드 작성이 귀찮으면 : 재사용할 수 있는 전달클래스 만들기

 

4. 상속과 컴포지션

하위 클래스가 상위 클래스의 '진짜' 하위 타입인 상황에서만 쓰자. 클래스간 관계가 is-a 관계일때만 상속

컴포지션을 사용해야할 상황에서 상속을 사용하는 건 내부 구현을 불필요하게 노출하는 것이다.
API가 내부 구현에 묶이고 클래스의 성능도 영원히 제한된다.

상속을 결정하는 질문

  • 확장하는 클래스의 API에 아무런 결함이 없는가?
  • 결함이 있다면 이 결함이 내 클래스의 API까지 전파되도 괜찮은가?
    : 컴포지션으로는 이런 결함을 숨길 수 있지만, 상속은 아니다.

댓글

Comments

Dev Book Review/Effective Java

[Effective Java] item17. 변경가능성을 최소화하라

불변 클래스 : 그 인스턴스의 내부 값을 수정할 수 없는 클래스 예 ) String, Wrapper Class, BigInteger, BigDecimal - 설계 구현 사용이 쉽다. 오류 여지가 적고 안전하다. 1. 불변 클래스 5가지 규칙 ㄱ. 객체의 상태를 변경하는 메서드(변경자)를 제공하지 않는다. (setter) ㄴ. 클래스를 확장할 수 없게 한다. (extend X) ㄷ. 모든 필드를 final로 선언한다 ㄹ. 모든 필드를 private으로 선언한다. ​ [아이템 15] : 클래스와 멤버의 접근권한을 최소화하라 ​ [아이템 16] : public 클래스에서는 public 필드가 아닌 접근자 메서드를 사용하라 ㅁ. 자신 외에는 내부의 가변 컴포넌트에 접근할 수 없도록 한다. 객체 참조 X - ge..

[Effective Java] item17. 변경가능성을 최소화하라

728x90

불변 클래스 : 그 인스턴스의 내부 값을 수정할 수 없는 클래스

  • 예 ) String, Wrapper Class, BigInteger, BigDecimal - 설계 구현 사용이 쉽다.
  • 오류 여지가 적고 안전하다.

 

1. 불변 클래스 5가지 규칙

ㄱ. 객체의 상태를 변경하는 메서드(변경자)를 제공하지 않는다. (setter)

ㄴ. 클래스를 확장할 수 없게 한다. (extend X)

ㄷ. 모든 필드를 final로 선언한다

ㄹ. 모든 필드를 private으로 선언한다.

[아이템 15] : 클래스와 멤버의 접근권한을 최소화하라
[아이템 16] : public 클래스에서는 public 필드가 아닌 접근자 메서드를 사용하라

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

객체 참조 X - getter가 그대로 참조값 반환 X - 생성자, 접근자, readObject에서 방어적 복사
[아이템 88] : readObject 메서드는 방어적으로 작성하라

 

2. 불변 객체의 장점

ㄱ. 근본적으로 스레드 안전 : 동기화 할 필요가 없다.

 

ㄴ. 불변객체는 안심하고 공유 가능 : 스레드 간 영향을 주고받을 수 없기 때문

방어적 복사도 필요없다 > clone 메서드 복사 생성자 필요 없다
String 클래스의 복사생성자는 되도록 사용하지 말자

[아이템 50] 적시에 방어적 복사본을 만들라
[아이템 13] clone 재정의는 주의해서 진행하라
[아이템 6] 불필요한 객체 생성을 피하라

 

ㄷ. 한번 만든 인스턴스 최대한 재활용 가능

자주 사용되는 인스턴스를 생성하여, 정적팩터리를 제공할 수 있다.

[아이템 1] 생성자 대신 정적 팩터리 메서드를 고려하라

메모리 사용량 감소 / 가비지 컬렉션 비용 감소
예시) BigInteger, Wrapper Class

 

ㄹ. 불변객체는 자유롭게 공유할 수 있음은 물론, 불변 객체끼리는 내부 데이터를 공유할 수 있다.

BigInteger의 음수를 구하는 로직에서, mag 배열은 새로 만들지 않고 참조값을 넘기는 것을 볼 수 있다.

 

ㅁ. 객체를 만들 때 다른 불변 객체들을 구성요소로 사용하면 이점이 많다 : 구조가 복잡해도 불변식 유지가 수월

 

ㄹ. 불변객체는 그 자체로 실패 원자성을 제공한다

실패 원자성 : 메서드에서 예외가 발생한 후에도 그 객체는 메서드 호출전 상태와 같은 유효한 상태를 가진다.
[아이템 76] 가능한한 실패 원자적으로 만들어라

public Object pop(){
    if (size == 0)
        throw new EmptyStackException();
    Object result = element[--size];
    elements[size];
    return result;
}

이부분을 제거해도 스택이 비었다면 여전히 예외를 던진다 다만 size의 값이 음수가되어 다음번 호출도 실패하게 만들며, 이때 던지는 ArrayIndexOutOfboundsException은 추상화 수준이 상황에 어울리지 않는다.

 

3. 불변 객체의 단점

값이 다르면 반드시 독립된 객체로 만들어야 한다.값의 가짓수가 많으면 이를 모두 만드는데 큰 비용이 필요하다.

BigInteger의 flipBit 메서드 (O(N))

BitSet의 flip 메서드 (O(1))

3-1. 흔히 쓰일 다단계 연산들을 예측이 될 때.

다단계 연산 속도를 높여주는 가변 동반클래스를 package-privated으로 둔다.

BigInteger는 연산 속도를 높여주는 동반 클래스로 package-privated로 되어있는 MutableBigInteger / SignedMutableBigInteger / BitSieve를 사용할 것으로 예상

 

3-2. 흔히 쓰일 다단계 연산들이 예측이 안될 때.

다단계 연산 속도를 높여주는 가변 동반클래스를 public 으로 둔다.


예시 ) String 클래스의 연산속도를 높이기 위함 : StringBuilder / StringBuffer

참고 : https://github.com/Java-Bom/ReadingRecord/issues/47

 

4. 불변클래스를 만드는 설계 방법

ㄱ. final 클래스 : 상속을 막는다.

ㄴ. 정적 팩터리를 제공하는 방법

모든 생성자를 private 혹은 package-private으로 만들고 public 정적 팩터리를 제공한다. (캐싱 기능 추가도 가능하다.) public이나 protected 생성자가 없으니 다른 패키지에서는 이 클래스를 확장하는게 불가능하기 때문이다.

[아이템 1] 생성자 대신 정적 팩터리 메서드를 고려하라

 

5. BigInteger, BigDecimal 설계시 주의점

이 두 클래스의 메서드가 모두 재정의할 수 있게 설계되어있다.
인수로 받은 객체가 '진짜'인지 확인하고, 이 인수들은 가변으로 가정하고 방어적으로 복사해서 사용해야한다.

[아이템 50] 적시에 방어적 복사본을 만들라

참고 : https://github.com/Java-Bom/ReadingRecord/issues/48

 

6. 불변 객체 기준 완화

"어떤 메서드도 객체의 상태 중 외부에 비치는 값을 변경할 수 없다."
계산 비용이 큰 값을 나중에 계산하여 final 이 아닌 필드에 캐싱해둔다.

실제로 String 클래스는 불변객체이지만, final이 아닌 필드 hash를 이용해 캐시하여 hashcode 재 연산 비용을 줄인다.
[아이템 83] 지연초기화는 신중히 사용하라

참고 : https://github.com/Java-Bom/ReadingRecord/issues/51

 

7. 정리

  • 클래스가 꼭 필요한 경우가 아니면 불변이어야 한다. (getter가 있다고 해서 setter를 무조껀 만들지는 말자)
  • 무거운 값객체의 경우 성능때문에 어쩔수 없다면, 불변 클래스와 쌍을 이루는 가변 동반 클래스를 public 클래스로 제공하자.
    [아이템 67] 최적화는 신중히 하라
  • 불변으로 만들 수 없는 클래스라도 변경할 수 있는 부분을 최소한으로 줄이자
  • 다른 합당한 이유가 없다면 모든 필드는 private final이어야 한다.
  • 생성자는 불변식 설정이 모두 완료된, 초기화가 완벽히 끝난 상태의 객체를 생성해야한다.

댓글

Comments

Dev Book Review/Effective Java

[Effective Java] Item16. public 클래스에서는 public 필드가 아닌 접근자 메서드를 사용하라

1. public 클래스의 가변 필드 절대 가변 필드를 public으로 노출하면 안된다. 캡슐화의 이점을 제공하지 못한다. API를 수정하지 않고는 내부 표현을 바꿀 수 없다. 불변식을 보장할 수 없다. 외부에서 필드에 접근할 때 부수 작업을 수행할 수도 없다. 패키지 바깥에서 접근할 수 있는 클래스라면 접근자(getter)를 제공하자. 클래스 내부 표현 방식을 언제든 바꿀 수 있다. package-private 클래스, private 중첩 클래스 는 데이터 필드를 노출해도 하등의 문제가 없다. package-private : 패키지 바깥 코드를 손대지 않고 데이터 표현 방식을 바꿀 수 있다. private 중첩 : 이 클래스를 포함하는 외부 클래스까지로 제한한다. 2. public 클래스의 불변 필드 ..

[Effective Java] Item16. public 클래스에서는 public 필드가 아닌 접근자 메서드를 사용하라

728x90

1. public 클래스의 가변 필드

절대 가변 필드를 public으로 노출하면 안된다.

  • 캡슐화의 이점을 제공하지 못한다.
  • API를 수정하지 않고는 내부 표현을 바꿀 수 없다.
  • 불변식을 보장할 수 없다.
  • 외부에서 필드에 접근할 때 부수 작업을 수행할 수도 없다.

패키지 바깥에서 접근할 수 있는 클래스라면 접근자(getter)를 제공하자.
클래스 내부 표현 방식을 언제든 바꿀 수 있다.

package-private 클래스, private 중첩 클래스 는 데이터 필드를 노출해도 하등의 문제가 없다.

  • package-private : 패키지 바깥 코드를 손대지 않고 데이터 표현 방식을 바꿀 수 있다.
  • private 중첩 : 이 클래스를 포함하는 외부 클래스까지로 제한한다.

 

2. public 클래스의 불변 필드

직접 노출할 때의 단점이 조금 줄어들지만 여전히 좋은 생각은 아니다.

단1. API를 변경하지 않고는 표현방식을 바꿀 수 없다.
단2. 필드를 읽을 때 부수적인 작업을 수행할 수 없다.

장1. 불변식을 보장할 수 있다.

댓글

Comments

Dev Book Review/Effective Java

[Effective Java] item 15. 클래스와 멤버의 접근 권한을 최소화하라

잘 설계된 컴포넌트 : 클래스 내부 데이터와 내부 구현정보를 외부 컴포넌트로부터 얼마나 잘 숨겼는가 오직 API를 통해서만 다른 컴포넌트와 소통한다. [정보은닉, 캡슐화] 1. 정보 은닉의 장점 시스템 개발 속도를 높인다 - 여러 컴포넌트 병렬 개발 시스템 관리 비용을 낮춘다 - 컴포넌트 교체 비용 하락 성능 최적화에 도움이 된다 - 완성된 시스템을 프로파일리하고 최적화할 컴포넌트만 가능하다. [아이템 67 - 최적화는 신중히 하라] 소프트웨어 재사용성을 높인다 - 의존성이 낮은 컴포넌트라면 다른 환경에서도 유용 큰 시스템을 제작하는 난이도를 낮춘다 - 개별 컴포넌트 동작 검증 2. 정보 은닉 기본원칙 모든 클래스와 멤버의 접근성을 가능한 한 좁힌다 (최대한 public을 지양하는 듯) 접근성 : 요소가..

[Effective Java] item 15. 클래스와 멤버의 접근 권한을 최소화하라

728x90

잘 설계된 컴포넌트 : 클래스 내부 데이터와 내부 구현정보를 외부 컴포넌트로부터 얼마나 잘 숨겼는가
오직 API를 통해서만 다른 컴포넌트와 소통한다. [정보은닉, 캡슐화]

 

1. 정보 은닉의 장점

  1. 시스템 개발 속도를 높인다 - 여러 컴포넌트 병렬 개발
  2. 시스템 관리 비용을 낮춘다 - 컴포넌트 교체 비용 하락
  3. 성능 최적화에 도움이 된다 - 완성된 시스템을 프로파일리하고 최적화할 컴포넌트만 가능하다.
    [아이템 67 - 최적화는 신중히 하라]
  4. 소프트웨어 재사용성을 높인다 - 의존성이 낮은 컴포넌트라면 다른 환경에서도 유용
  5. 큰 시스템을 제작하는 난이도를 낮춘다 - 개별 컴포넌트 동작 검증

 

2. 정보 은닉 기본원칙

모든 클래스와 멤버의 접근성을 가능한 한 좁힌다 (최대한 public을 지양하는 듯)
접근성 : 요소가 선언된 위치 + 접근 제한자 (private, protected, public)

 

ㄱ. 톱레벨 클래스, 인터페이스

public : 공개 api / package-private : 패키지 안에서만 사용

package-private : 클라이언트에 피해 없이 다음 릴리즈에서 수정,교체,제거 가능

 

ㄴ. private static 중첩 클래스

한 클래스에서만 사용하는 package-private 클래스의 경우에는 private staticd으로 중첩시키자
[아이템 24 - 멤버클래스는 되도록 static으로 만들어라]

 

ㄷ. 멤버필드

private / package-private / protected / public

  1. private 으로 만들기
  2. package-private으로 멤버 접근 수준 풀기
  3. 권한 풀기가 잦다 : 컴포넌트 분해 여부를 좀 더 고민한다.
    Serializabl을 구현한 클래스에서는 의도치 않게 (package-private, private 멤버가) 공개 API가 될 수 있으니 조심
  4. protected로 멤버 접근 수준 풀기

Public 클래스의 protected 멤버는 공개 API이므로 주의
[아이템 19 - 상속을 고려해 설계하고 문서화하라. 그러지 않았다먼 상속을 금지하라]

 

3. 멤버 접근성 제약

상위클래스의 메서드를 재 정의할 때 상위보다 좁게 설정을 할 수 없다.

[아이템10 - equals는 일반 규약을 지켜 재정의하라] : 리스코프 치환원칙 (상위클래스 인스턴스는, 하위클래스 인스턴스로 대체가 가능해야한다.)

 

4. 주의점

코드 테스트만을 위해 클래스, 인터페이스, 멤버를 공개 API로 만들면 안된다.

 

ㄱ. public 클래스의 인스턴스 필드는 되도록 public이 아니어야한다.

[아이템 16 - public 클래스에서는 public 필드가 아닌 접근자 메서드를 사용하라]

필드와 관련된 모든 것의 불변성을 잃기 때문이다.

public 가변 필드를 갖는 클래스는 스레드 안전하지 않다.

심지어 필드가 final이면서 불변 객체를 참조하더라도 문제는 여전히 남는다. 내부 구현을 바꾸고 싶어도 그 public 필드를 없애는 방식으로는 리팩터링 할 수 없다

헷갈렸지만 : 어찌됐든 public필드일 경우는 필드를 없애는 방식의 리팩터링이 안되서 쓰지 말라는 내용으로 이해함

정적필드도 마찬가지이다.

 

ㄴ. 정적필드에서의 public 필드 예외

public static final 필드 공개는 허용한다. 이런 필드는 반드시 기본 타입 값이나 불변 객체를 참조해야 한다.
[아이템 68 - 일반적으로 통용되는 명명 규칙을 따르라]
[아이템 17 - 변경 가능성을 최소화하라]

 

ㄷ. 클래스에서 public static final 배열필드를 두거나 이 필드를 반환하는 접근자 메서드를 제공해서는 안된다.

클라이언트에서 배열의 내용을 수정할 수 있기 때문이다.

[권장 방법]

1. public 불변리스트를 추가한다.

private static final Thing[] PRIVATE_VALUES = {...};
public static final List<Thing> VALUES = Collections.unmodifiableList(Arrays.asList(PRIVATE_VALUES));

2. 복사본을 반환하는 public 메서드를 추가한다 : 방어적 복사

private static final Thing[] PRIVATE_VALUES = {...};
public static final Thing[] values(){
  return PRIVATE_VALUES.clone();
}

타입, 성능에 따라서 사용하자.

 

5. Java 9 : 모듈 시스템 개념의 도입

패키지 : 클래스의 묶음 // 모듈 : 패키지의 묶음

모듈 : 자신에 속하는 패키지 중 공개 (export) 할 것들을 선언한다. [module-info.java 파일에]

클래스를 외부에 공개하지 않으면서도 같은 모듈을 이루는 패키지 사이에서 자유롭게 공유할 수 있다.

암묵적 접근 수준 : public, protected 수준의 효과가 모듈 내부로 한정된다.

 

ㄱ. 모듈에 적용되는 새로운 두 접근 수준은 주의해야한다.

모듈의 jar파일을 모듈 경로가 아니라 애플리케이션 클래스 패스에 두면, 모듈안 패키지가 모듈이 없는 것 처럼 행동한다.

일반 클래스 파일이 있는 것처럼 행동한다 : 모듈 시스템 암묵적 접근 수준이 해제된다

JDK : 자바라이브러리에서 공개하지 않은 패키지는 모듈 밖에서 접근 할 수 없다.

 

ㄴ. 모듈의 장점을 누리기 위한 조치

  • 패키지를 모듈 단위로 묶는다.
  • 모듈 선언에 패키지들의 의존성을 명시한다.
  • 소스트리 재배치
  • 모듈 안으로 부터 (모듈 시스템을 적용하지 않는) 일반 패키지로의 모든 접근에 특별한 조치를 취해야한다.

그러나 모듈의 개념은 아직은 사용하지 않는게 좋은 것 같다.

댓글

Comments

Dev Book Review/Effective Java

[Effective Java] Chapter 3: 모든 객체의 공통 메서드

Item10. equals는 일반 규약을 지켜 재정의하라 필요한 경우가 아니라면 equals를 재정의 하지말자. 많은 경우에 Object의 equals가 우리가 원하는 비교를 정확히 수행해준다. 재정의해야 할 때는 그 클래스의 핵심필드 모두를 빠짐없이 5가지 규약을 지켜가며 비교해야한다. equals의 5가지 규약 : 반사성, 대칭성, 추이성, 일관성, null-아님 Link : https://jyami.tistory.com/66 item10. equals는 일반 규약을 지켜 재 정의하라 1. equals를 재정의 하면 안되는 경우 equals는 재정의하기 쉬워보이지만 곳곳에 함정이 있다. 문제를 회피하는 가장 쉬운 길은 아예 재정의하지 않는 것 a. 각 인스턴스가 본질적으로 고유할 때 값 표현 객체가...

[Effective Java] Chapter 3: 모든 객체의 공통 메서드

728x90

Item10. equals는 일반 규약을 지켜 재정의하라

  • 필요한 경우가 아니라면 equals를 재정의 하지말자.
  • 많은 경우에 Object의 equals가 우리가 원하는 비교를 정확히 수행해준다.
  • 재정의해야 할 때는 그 클래스의 핵심필드 모두를 빠짐없이 5가지 규약을 지켜가며 비교해야한다.
  • equals의 5가지 규약 : 반사성, 대칭성, 추이성, 일관성, null-아님
  • Link : https://jyami.tistory.com/66
 

item10. equals는 일반 규약을 지켜 재 정의하라

1. equals를 재정의 하면 안되는 경우 equals는 재정의하기 쉬워보이지만 곳곳에 함정이 있다. 문제를 회피하는 가장 쉬운 길은 아예 재정의하지 않는 것 a. 각 인스턴스가 본질적으로 고유할 때 값 표현 객체가..

jyami.tistory.com

 

 

Item11. equals를 재정의하려거든 hashCode도 재정의하라

  • equals를 재정의할 때는 hashCode도 반드시 재정의해야 한다. (프로그램이 제대로 동작해야하므로)
  • 재정의한 hashCode는 Object의 API 문서에 기술된 일반 규약을 따라야한다.
  • 3번에 써둔 좋은 HashCode를 작성하는 방법을 참고하자
  • 서로 다른 인스턴스라면 되도록 해시코드도 서로 다르게 구현해야한다.
  • AutoValue 프레임워크를 사용하면 멋진 equals와 hashCode를 자동으로 만들어준다.
  • Link : https://jyami.tistory.com/67
 

Item11. equals를 재정의하려거든 hashCode도 재정의하라

equals를 재정의한 클래스 모두에서 hasCode도 재정의해야한다. 일반 규약을 어기게 되어 HashMap이나 HashSet 같은 컬렉션 원소로 사용할 때 문제를 일으킬 수 있다. 1. Object 명세의 3가지 규약 equals() 에 사..

jyami.tistory.com

 

Item12. toString을 항상 재정의하라

  • 모든 구체 클래스에서 Object의 toString을 재정의하자.
  • 상위 클래스에서 이미 알맞게 재정의한 경우에는 예외다.
  • toString을 재정의한 클래스는 사용하기도 즐겁고 그 클래스를 사용한 시스템 디버깅에 용이하다.
  • toString은 해당 객체에 관한 명확하고 유용한 정보를 읽기 좋게 반환해야한다.
  • Link : https://jyami.tistory.com/68
 

Item12. toString을 항상 재정의하라

Object.toString() 메서드 : [클래스이름]@[16진수로 표시한 해시코드] 포맷을 갖는다. 예시 ) PhnoneNumber@adbbd 1. toString() 규약 1) 간결하면서 사람이 읽기 쉬운 형태의 유익한 정보 2) 모든 하위 클래스에..

jyami.tistory.com

 

Item13. clone 재정의는 주의해서 진행하라

  • Cloneable이 몰고 온 모든 문제를 되짚어봤을 때, 새로운 인터페이스를 만들 때는 절대 Cloneable을 확장해서는 안되며, 새로운 클래스도 이를 구현해서는 안된다.
  • final 클래스라면 Clonealbe을 구현해도 위험이 크지 않지만, 성능 최적화 관점에서 검토후 별다른 문제가 없을 대만 드물게 허용하자
  • 기본 원칙은 '복제 기능은 생성자와 팩터리를 이용하는게 최고'라는 것이다.
  • 단, 배열만은 clone 메서드 방식이 가장 깔끔한, 이 규칙의 합당한 예외라 할 수 있다.
  • Link : https://jyami.tistory.com/69
 

item13. clone 재정의는 주의해서 진행하라

1. Cloneable interface cloneable : 복제해도 되는 클래스임을 명시하는 용도의 믹스인 인터페이스이다. 믹스인 : 클래스가 자신의 "본래 타입"에 추가하여 구현할 수 있는 타입. 선택 가능한 기능을 제공하며,..

jyami.tistory.com

 

Item14. Comparable을 구현할지 고려하라

  • 순서를 고려해야하는 값 클래스를 작성한다면 꼭 Comparable 인터페이스를 구현하여, 그 인스턴스들을 쉽게 정렬하고, 검색하고, 비교 기능을 제공하는 컬렉션과 어우러지도록 해야 한다.
  • compareTo 메서드에서 필드의 값을 비교할 때 < 와 > 연산자는 쓰지 말자.
  • 그대신 박싱된 기본 타입 클래스가 제공하는 정적 compare 메서드나, Comparator 인터페이스가 제공하는 비교자 생성 메서드를 사용하자
  • Link : https://jyami.tistory.com/70
 

Item 14. Comparable을 구현할지 고려하라

1. compareTo()와 equals()의 차이 compareTo는 Object의 메서드가 아니다. 성격은 두가지만 빼면 Object의 equals와 같다. compareTo는 단순 동치성 비교에 더해 순서까지 비교 가능하다. 그 클래스의 인스턴스들..

jyami.tistory.com

 

댓글

Comments

Dev Book Review/Effective Java

[Effective Java] item 14. Comparable을 구현할지 고려하라

1. compareTo()와 equals()의 차이 compareTo는 Object의 메서드가 아니다. 성격은 두가지만 빼면 Object의 equals와 같다. compareTo는 단순 동치성 비교에 더해 순서까지 비교 가능하다. 그 클래스의 인스턴스들에 자연적인 순서가 있음을 뜻할 수 있다. 그래서 Comparable을 구현한 객체들의 배열은 손쉬운 정렬이 가능하다. 알파벳, 숫자, 연대 등 순서가 명확한 값 클래스 작성할땐 구현하자. 2. CompareTo() 메서드 규약 equals와 같은 내용이다 (주의점, 우회법 모두 같다.) this object : 1 ㄱ. 반사성, 대칭성, 추이성을 충족해야한다 ● sgn(x.compar..

[Effective Java] item 14. Comparable을 구현할지 고려하라

728x90

1. compareTo()와 equals()의 차이

compareTo는 Object의 메서드가 아니다.

성격은 두가지만 빼면 Object의 equals와 같다.

  • compareTo는 단순 동치성 비교에 더해 순서까지 비교 가능하다.
  • 그 클래스의 인스턴스들에 자연적인 순서가 있음을 뜻할 수 있다.

그래서 Comparable을 구현한 객체들의 배열은 손쉬운 정렬이 가능하다.
알파벳, 숫자, 연대 등 순서가 명확한 값 클래스 작성할땐 구현하자.

 

2. CompareTo() 메서드 규약

equals와 같은 내용이다 (주의점, 우회법 모두 같다.)

this < object : -1
this == object : 0
this > object : 1

 

ㄱ. 반사성, 대칭성, 추이성을 충족해야한다

sgn(x.compareTo(y) == -sgn(y.compareTo(x))

x.compareTo(y)y.compareTo(x)가 예외를 던질 때에 한해 예외를 던진다.
두 객체 참조의 순서를 바꾸어 비교해보아도 예상한 결과가 나와야한다.

x.compareTo(y) > 0 && y.compareTo(z) > 0이면 x.compareTo(z) > 0 이다.

첫번째가 두번째보다 크고 두번째가 세번째보다 크면, 첫번째는 세번째보다 크다.

 Comparalbe을 구현한 클래스는 모든 z에 대해 x.compareTo(y) == 0 이면 sgn(x.compareTo(z)) == sgn(y.compareTo(z)) 이다.

크기가 같은 객체들끼리 어떤 객체와 비교해도 항상 같아야한다.

(x.compareTo(y) == 0) == (x.equals(y)) 여야 한다.

compareTo로 수행한 동치성 테스트의 결과가 equals와 같아야한다.
지키지 않을 때는 명시해야한다. "주의 : 이 클래스의 순서는 equals 메서드와 일관되지 않는다."

컬렉션 구현 인터페이스(Collection, Set, Map) - 구현에 따른 주의가 필요하기 때문이다.
: equals 메서드 규약을 따른다 되어있다.
: 정렬된 컬렉션들은 동치성 비교시 equals대신 compareTo 사용

 

ㄴ. 기존 클래스를 확장한 구체클래스에서 새로운 값 컴포넌트를 추가하면 compareTo 지킬 방법이 없다.

우회법 : 컴포지션을 사용하고 + '뷰' 메서드를 제공하자

 

3. compareTo 메서드 작성요령

equals와의 차이점만 주의하면 된다.

 Comparable은 타입을 인수로 받는 제네릭 인터페이스이다 : 메서드의 인수타입은 컴파일타임에 정해진다.

 compareTo 메서드는 필드의 동치가 아니라 순서를 비교한다.Comparable을 구현하지 않았다면, Comparator를 사용할 수 있다.

 compareTo 메서드 구현시 관계연산자 <, > 사용하는 방식은 거추장 스럽고 오류를 유발한다. [Java7]

// 아래 방법을 사용하자.
Integer.compare(a,b);
Float.compare(a,b);
Double.compare(a,b);

   클래스의 핵심필드 여러개중 어떤것을 먼저 비교할 지에 대해 집중하라

 비교자 생성 메서드(comparator construction method)와 팀을 꾸려 메서드 연쇄로 비교자를 생성. [Java 8]

private static final Comparator<PhoneNumber> COMPARATOR =
  comparingInt((PhoneNumber pn)->pn.areaCode)	// Comparator의 인스턴스 메서드
  	.thenComparingInt(pn -> pn.prefix)			// 원하는 만큼 연달아 호출 가능
		.thenComparingInt(pn -> pn.lineNumber);

public int compareTo(PhoneNumber pn){
  return COMPARATOR.compare(this, pn);
}

    이 람다에서 입력 인수의 타입을 명시해 주었다. (프로그램 컴파일을 도와준 것과 같다.)

 Comparator의 보조 생성 메서드

  comparingLong, thenComparingLong
  comparingDouble, thenComparingDouble

 값의 차를 이용한 compareTo, compare 메서드를 사용하지 말자.

  정수 오버플로 / 부동 소수점 계산 방식 오류  
  개선 : Integer.compare(a,b); || Comparator.comparingInt(x -> x.hashCode())

댓글

Comments