Error rendering WebPanel: No renderer found for resource type: velocity Template contents: <meta name="ajs-keyboardshortcut-hash" content="$keyboardShortcutManager.shortcutsHash">
메타 데이터의 끝으로 건너뛰기
메타 데이터의 시작으로 이동

이 페이지의 이전 버전을 보고 있습니다. 현재 버전 보기.

현재와 비교 페이지 이력 보기

« 이전 버전 4 다음 »

[3장 모든 객체의 공통 메서드]

object에서 final이 아닌 메서드 (equals, hashCode, toString, clone, finalize)는 모두 재정의overriding을 염두에 두고 설계되었다.

일반 규약에 맞게 재정의 해야 규약을 준수한다고 가정하는 클래스들(HashMap, HashSet등)이 오작동을 하지 않는다.

이번 장에서는 Object메서드들을 언제 어떻게 재정의해야 하는지를 다룬다.

Comparable.compareTo의 Object의 메서드는 아니지만 성격이 비슷하여 함께 다룬다.


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

equals 메서드는 재정의하기 쉬워 보이지만 함정이 도사리고 있다.(젤 쉬운건 재정의 안하기…)

Default는 그 클래스의 인스턴스는 오직 자기 자신과만 같게한다.

So, 다음 상황중 하나에 해당하면 재정의 안하도록..!

  • 각 인스턴스가 본질적으로 고유하다.
  • 인스턴스의 ‘논리적 동치성(logical equality)’을 검사할 일이 없다.
  • 상위 클래스에서 재정의한 equals가 하위 클래스에도 딱 들어맞는다.
  • 클래스가 private이거나 package-private이고 equals 메서드를 호출할 일이 없다.

equals가 실수로라도 호출되는걸 막고싶다면

@Override public boolean equals(Object o){
    throw new AssertionError(); //호출금지!
}

그렇다면 재정의 해야 할때?

  • 객체 식별성이 아니라 논리적 동치성을 확인해야 할 때

ex) 값 클래스들(Integer, String…) 값이 같은지를 알고싶어 할테니까 !

but, 값 클래스여도 값이 같은 인스턴스가 둘 이상 만들어지지 않음을 보장하는 인스턴스 통제클래스라면 재정의 안해도 OK.

ex)Enum 논리적 동치성 = 객체 식별성

Object 명세에 적힌 규약

equals 메서드는 동치관계를 구현하며 다음을 만족한다.

  • 반사성 : null이 아닌 모든 참조 값 x에 대해 x.equals(x) 는 true다.
  • 대칭성 : null이 아닌 모든 참조 값 x, y에 대해 x.equals(y)가 true면 y.equals(x)도 true다.
  • 추이성 : null이 아닌 모든 참조 값 x, y, z에 대해 x.equals(y)가 true 이고 y.equals(z) 도 true면 x.equals(z)도 true다.
  • 일관성 : null이 아닌 모든 참조 값 x, y에 대해 x.equals(y)를 반복해서 호출하면 항상 같은 결과를 반환한다.
  • null 아님 : null이 아닌 모든 참조 값 x에 대해 x.equals(null)은 false다.

동치관계 : 집합을 서로 같은 원소들로 이루어진 부분집합으로 나누는 연산이다.

equals메소드가 쓸모있으려면 모든 원소가 같은 동치류에 속한 어떤 원소와도 서로 교환할 수 있어야 한다.

양질의 equals 메서드 구현 방법 정리

  1. == 연산자를 사용해 입력이 자기 자신의 참조인지 확인한다.
  2. instanceof 연산자로 입력이 올바른 타입인지 확인한다.
  3. 입력을 올바른 타입으로 형변환한다.
  4. 입력 객체와 자기 자신의 대응되는 ‘핵심’필드들이 모두 일치하는지 하나씩 검사한다.

덧, 어떤 필드를 먼저 비교하느냐가 equals의 성능을 좌우하기도 한다.

equals를 다 구현했다면 세 가지만 자문해보자. 대칭적인가? 추이성이 있는가? 일관적인가?

자문에서 끝내지 말고 단위 테스트를 작성해 돌려보자. (AutoValue 프레임워크 이용 추천)

마지막 주의사항

  • equals를 재정의할 땐 hashCode도 반드시 재정의하자.
  • 너무 복잡하게 해결하려 들지 말자.
  • Object 외의 타입을 매개변수로 받는 equals 메서드는 선언하지 말자.

    // 잘못된 예 - 입력 타입은 반드시 Object 여야 한다 !
    // 이 메서드는 Object.equals를 재정의 한게 아니라 다중정의한 것이다.
    // 기본 equals를 그대로 둔 채 추가한 것일지라도, 타입을 구체적으로 명시한 equals는 오히려 해가 된다.
    public boolean equals(MyClass o){
      ...
    }

item11 - equals를 재정의하려거든 hashCode도 재정의하라

equals를 재정의한 클래스 모두에서 hashCode도 재정의해야 한다.

hashCode를 잘못 재정의 했을때 크게 문제가 되는 조항은 아래 두번째 조항. 즉, 논리적으로 같은 객체는 같은 해시코드를 반환해야 한다.

Object 명세에서 발췌한 규약

  • 애플리케이션이 실행되는 동안 한 객체애 대헌 hashCode 메서드는 몇번을 호출해도 일관되게 항상 같은 값을 반환해야 한다.
  • equals(Object)가 두 객체를 같다고 판단했다면 두 객체의 hashCode는 똑같은 값을 반환해야 한다.
  • 두 객체가 다르다고 판단했더라도 서로 다른 hashCode를 반환할 필요는 없다. but, 좋은 해시 함수는 서로 다른 인스턴스에 다른 해시코드를 반환한다. 그게 성능도 더 좋다.

좋은 hashCode를 작성하는 간단한 요령

  1. int 변수 result를 선언한 후 값 c로 초기화한다.
  2. 해당 객체의 나머지 핵심 필드 f 각각에 대해 다음 작업을 수행한다.

    a. 해당 필드의 해시코드 c를 계산한다.

    b. 위에서 계산한 해시코드 c로 result를 갱신한다. result = 31* result + c;

  3. result를 반환한다.


다 구현했다면 이 메서드가 동치인 인스턴스에 대해 똑같은 해시 코드를 반환할지 자문하고 테스트를 해보자.(AutoValue 굳)

파생필드는 해시코드 계산에서 제외OK. equals 비교에 사용되지 않은 필드는 반드시 제외해야 한다.

클래스가 불변이고 해시코드를 계산하는 비용이 크다면 캐싱하는 방식을 고려해야 한다.

해시의 키로 사용되지 않는 경우라면 hashCode가 처음 불릴 때 계산하는 지연 초기화 전략도 있다.(스레드 안전성 고려)

성능을 높인답시고 해시코드를 계산할 때 핵심 필드를 생략해서는 안된다.

hashCode가 반환하는 값의 생성 규칙을 API사용자에게 자세히 공표하지 말자. 그래야 클라이언트가 이 값에 의지하지 않게 되고 추후에 계산 방식을 바꿀 수도 있다.


item12 - toString을 항상 재정의하라

기본 toString메서드는 단순히 클래스_이름@16진수로_표현한_해시코드 를 반환한다.

간결하면서 사람이 읽기 쉬운 형태의 유익한 정보 를 반환한다.

가 toString의 일반 규약이며 모든 하위 클래스에서 이 메서드를 재정의하라고 한다.

why? toString을 잘 구현한 클래스는 사용하기에 훨씬 즐겁고, 그 클래스를 사용한 시스템은 디버깅하기 쉬우니까 !

toString메서드는 객체를 println, printf, 문자열 연결 연산자(+), assert 구문에 넘길 때, 혹은 디버거가 객체를 출력할 때 자동으로 불린다. (어딘가에서 쓰인다!)

// PhoneNumber용 toString을 제대로 재정의했다면 이 코드만으로 충분하다.
System.out.println(phoneNumber + "에 연결할 수 없습니다.");

실전에서 toString은 그 객체가 가진 주요 정보 모두를 반환하는게 좋다.

toString의 반환값의 포맷을 문서화할지 정해야 한다.

포맷을 명시하기로 했다면, 명시한 포맷에 맞는 문자열과 객체를 상호 전환할 수 있는 정적 팩터리나 생성자를 함께 제공해주면 좋다.(ex 값 클래스 BigInteger, BigDecimal와 대부분의 기본 타입 클래스)

장/단점은 있다. 그러나 포맷을 명시하든 아니든 의도는 명확히 밝혀야한다.

포맷 명시 여부와 상관없이 toString이 반환한 값에 포함된 정보를 얻어올 수 있는 API를 제공하자.

접근자를 제공하지 않으면 변경될 수 있다고 문서화했더라도 그 포맷이 사실상 준-표준 API나 다름없어진다.


마지막으로…

정적 유틸리티 클래스(아이템4)는 toString을 제공할 이유가 없다. 대부분의 열거타입(아이템34)도 이미 완벽한 toString이 제공되니 재정의 하지 않아도 된다.

하지만 하위 클래스들이 공유해야 할 문자열 표현이있는 추상클래스라면 재정의해줘야 한다. (컬렉션 구현체는 추상 컬렉션 클래스들의 toString메서드를 상속해 쓴다.)

AutoValue 프레임 워크는 toString도 생성해준다. 아무것도 알려주지 않는 Object toString보다 훨씬 유용하다.


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


item14 - 

[4장 클래스와 인터페이스]

추상화의 기본 단위인 클래스와 인터페이스는 자바 언어의 심장!! 그 설계에 사용하는 강력한 요소가 많이 있다.

이런 요소를 적절히 활용하여 클래스와 인터페이스를 쓰기 편하고, 견고하며, 유연하게 만드는 방법을 안내한다.

item15 - 클래스와 멤버의 접근 권한을 최소화하라

잘 설계된 컴포넌트?

클래스 내부 데이터와 내부 구현정보를 외부 컴포넌트로부터 얼마나 잘 숨겼느냐다. (정보은닉, 캡슐화) -> 구현과 API를 깔끔히 분리함

정보 은닉의 장점

  • 시스템 개발 속도를 높인다.
  • 시스템 관리 비용을 낮춘다.
  • 성능 최적화에 도움을 준다.
  • 소프트웨어 재사용성을 높인다.
  • 큰 시스템을 제작하는 난이도를 낮춰준다.

정보 은닉을 위한 다양한 장치? 접근 제한자 !

접근 제한자를 제대로 활용하는 것이 정보 은닉의 핵심이다.

모든 클래스와 멤버의 접근성을 가능한 한 좁혀야 한다.

소프트웨어가 올바로 동작하는 한 항상 가장 낮은 접근 수준을 부여해야 한다.

접근 수준 네 가지

  • private
  • package-private
  • protected
  • public

자바 9 에서부터 모듈 시스템이라는 개념이 도입되면서 추가된 암묵적 접근 수준 두가지

패키지중 공개(export)할 것들을 선언하게 되는데 공개하지 않으면 public 멤버라도 외부에서 접근X.. 효과가 모듈 내부로 한정되는 변종

모듈경로가 아닌 애플리케이션의 클래스패스에 두면 그 모듈 안의 모든 패키지는 모듈이 없는 것처럼 행동한다. (모두 밖에서 접근가능)

ex)JDK 자체가 적극 활용한 사례.


클래스의 공개 API를 세심히 설계한 후, 그외의 모든 멤버는 private으로.

테스트의 목적으로 적당한 수준으로 넓혀도 되지만 공개 API로 만들어서는 안 된다.

public 클래스는 상수용 public static final 필드 외에는 어떠한 public 필드도 가져서는 안된다.

public static final 필드가 참조하는 객체가 불변인지 확인하라.


item16 - public 클래스에서는 public 필드가 아닌 접근자 메서드를 사용하라


// 이처럼 퇴보한 클래스는 public이어서는 안 된다 !
// 데이터 필드에 직접 접근할 수 있어 캡슐화의 이점 제공 못함
class Point{
  public double x;
  public double y;
}

API를 수정하지 않고는 내부 표현을 바꿀 수 없고, 불변식도 보장할 수 없고, 외부에서 필드에 접근할 때 부수 작업을 수행할 수도 없다…

public 클래스의 필드가 불변인경우, 불변식만 보장할뿐 나머지 단점들은 갖고있게된다.


// 접근자와 변경자 메서드를 활용해 데이터를 캡슐화한다.
// public 클래스라면 이 방식이 확실히 맞다 !
class Point{
  private double x;
  private double y;
  
  public Point(double x, double y){
    this.x = x;
    this.y = y;
  }
  
  public double getX() { return x; }
  public double getY() { return y; }
  
  public void setX( double x ) { this.x = x; }
  public void setX( double y ) { this.y = y; }
}


패키지 바깥에서 접근할 수 있는 클래스라면 접근자를 제공함으로써 클래스 내부 표현 방식을 언제든 바꿀 수 있는 유연성을 얻을 수 있다.

package-private 클래스 혹은 private중첩 클래스라면 데이터 필드를 노출한다 해도 하등의 문제가 없다. 추상 개념만 올바르게 표현해준다면.

why? 패키지 바깥 코드는 전혀 손대지 않고도 데이터 표현방식을 바꿀 수 있어서.

(warning) 규칙을 어긴 사례 : java.awt.package 의 Point 와 Dimension. 흉내X 타산지석으로 삼길..



item17 - 변경 가능성을 최소화하라


불변 클래스:  인스턴스의 내부 값을 수정할 수 없는 클래스. 객체가 파괴되는 순간까지 절대 달라지지 않음.

    ex) String, 기본 타입의 박싱된 클래스들, BigInteger, BigDecimal .

불변 클래스는 가변 클래스보다 설계하고 구현하고 사용하기 쉬우며, 오류가 생길 여지도 적고 훨씬 안전하다.

클래스를 불변으로 만드는 규칙

  • 객체의 상태를 변경하는 메서드(변경자)를 제공하지 않는다.
  • 클래스를 확장할 수 없도록 한다.
  • 모든 필드를 final로 선언한다.
  • 모든 필드를 private으로 선언한다.
  • 자신 외에는 내부의 가변 컴포넌트에 접근할 수 없도록 한다.


Item 18 : 상속보다는 컴포지션을 사용하라

상속은 보통 코드를 재사용하는 강력한 수단이지만, 최선은 아니다. 오류를 만들어 내기 쉽다.

  • 매서드 호출과 달리 상속은 캡슐화를 깨뜨린다.

    → 상위 클래스가 어떻게 구현되느냐에 따라 하위클래스 동작이 달라질 수 있다.

상속의 잘못된 예
public class InstrumentedHashSet<E> extends HashSet<E> {
    // 추가된 원소의 수
    private int addCount = 0;

    public InstrumentedHashSet() {
    }

    public InstrumentedHashSet(int initCap, float loadFactor) {
        super(initCap, loadFactor);
    }

    @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) {
        InstrumentedHashSet<String> s = new InstrumentedHashSet<>();
        s.addAll(List.of("Snap", "Crackle", "Pop"));
        System.out.println(s.getAddCount());
    }

}

반환 예상 값 :

실제 출력 값 : 

  → InstrumentedHashSet에서 구현된 addAll()이 HashSet의 addAll() 메서드를 호출하기 때문이다.

      add() 메서드가 호출될때 위에서 재정의한 메서드가 호출된다. 


addAll method in HashSet
public boolean addAll(Collection<? extends E> c) {
        boolean modified = false;
        for (E e : c)
            if (add(e))
                modified = true;
        return modified;
    }


  • 안전한 방법은? 

     1. addAll 메서드를 재정의 하지 않는다 → HashSet의 addAll과 add가 어떻게 동작하는지 안다는 가정.

     2. addAll 메서드를 다른방식으로 재정의 → HashSet의 addAll을 호출하지 않는 방법( 첫번째 방법보다는 나음 )

     3. 매서드를 재정의하기 보다는 새로운 메서드를 추가해서 사용 → 문제가 될 가능성은 여전히 존재 ( 이름이 같은경우, return type 같은 경우)\

     4. 확장 대신 새로운 클래스를 생성, private 필드로 기존 클래스의 인스턴스를 참조 ( 기존 클래스가 새로운 클래스의 구성요소로 쓰인다는 의미로 이 같은 설계를 composition이라고 함.)

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("Snap", "Crackle", "Pop"));
        System.out.println(s.getAddCount());
    }
}




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);      }
	//... 생략


}
  • 다른 Set 인스턴스를 감싸고 있다는 뜻에서 InstrumentedSet 같은 클래스를 wrapper 클래스라고 하고 다른 Set에 측정, 기록 하는 기능을 덧씌운다는 뜻으로 Decorator pattern이라고 한다.
  • 상속은 is-a 관계를 만족하는 두개의 클래스에서만 구현해야 한다. (ex. A와 B 클래스 : B는 A인가? 를 만족)
  • Java에서 이 원칙을 위반한 클래스는 Stack 과 Vector, Properties 와 HashTable 이 있다고 한다.





Item 19 - 상속을 고려해 설계하고 문서화 하라

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


/**
* {@inheritDoc}
*
* @implSpec
* This implementation iterates over the collection looking for the
* specified element. If it finds the element, it removes the element
* from the collection using the iterator's remove method.
*
* <p>Note that this implementation throws an
* {@code UnsupportedOperationException} if the iterator returned by this
* collection's iterator method does not implement the {@code remove}
* method and this collection contains the specified object.
*
* @throws UnsupportedOperationException {@inheritDoc}
* @throws ClassCastException {@inheritDoc}
* @throws NullPointerException {@inheritDoc}
*/

iterator 매서드를 재정의하면 remove 메서드의 동작에 영향을 준다는 설명하는 주석

  • 상속용 클래스를 설계 할때는 직접 하위 클래스를 만들어서 검증이 필요하다.


  • 상속용 클래스의 생성자는 직,간접적으로든 재정의 가능 메서드를 호출해서는 안 된다.
상속용 클래스
// Class whose constructor invokes an overridable method. NEVER DO THIS! (Page 95)
public class Super {
    // Broken - constructor invokes an overridable method
    public Super() {
        overrideMe();
    }

    public void overrideMe() {
    }
}
하위 클래스
// Demonstration of what can go wrong when you override a method  called from constructor (Page 96)
public final class Sub extends Super {
    // Blank final, set by constructor
    private final Instant instant;

    Sub() {
        instant = Instant.now();
    }

    // Overriding method invoked by superclass constructor
    @Override public void overrideMe() {
        System.out.println(instant);
    }

    public static void main(String[] args) {
        Sub sub = new Sub();
        sub.overrideMe();
    }
}
  • 클래스를 확장해야할 이유가 명확하지 않다면 상속을 금지해라. 
  • 금지하는 방법으로는 final 선언, 생성자 모두를 외부에서 접근 할 수 없도록 변경.




item 20 : 추상 클래스보다는 인터페이스를 우선하라

  • 인터페이스와 추상클래스의 가장 큰 차이는 추상클래스를 구현한 하위클래스는 반드시 추상클래스의 하위 타입이 되어야한다는 것

  • 인터페이스는 믹스인(mixin) 정의에 적합하다.

    • mixin : 클래스가 구현할 수 있는 타입, 원래의 primary type 외에 특정 부가적인 행위를 제공하고 선언하는 효과를 준다(?)

      ex) Comparable 인터페이스는 자신을 구현한 클래스의 인스턴스끼리의 순서를 정할 수 있다고 선언하는 mixin interface


  • 인터페이스를 이용해서 계층구조가 없는 타입 프레임워크를 만들 수 있다.
    • 현실적으로 계층을 엄격히 구분하기 어려울때 사용하면 좋음 (ex singer, songwriter)
혼합구조
public interface Singer {
    AudioClip sing(Song s);
}


public interface Songwriter {
    Song compose(boolean hit);
}


public interface SingerSongwriter extends Songwriter, Singer{
   AudioClip strum();
   void actSensitive();
}



  • 인터페이스와 추상 골격 구현(skeletal implementation) 클래스를 함께 제공하여 2개의 장점을 모두 취하는 방법도 있다.
    • 인터페이스로는 타입을 정의, 디폴트 메서드를 제공
    • 흠.... 이부분은 좀 어렵다
    • 이런것이 템플릿 메서드 패턴이라고 한다.
    • 템플릿 메서드 패턴을 찾아본다면 이해할 수 있으려나..?



Item 21 : 인터페이스는 구현하는 쪽을 생각해 설계하라

  • 자바8이전에는 인터페이스에 메서드를 추가하면 보통 컴파일 오류가 발생했다.
    • 구현 클래스들에서 구현을 하지 않았기 때문
  • 자바8부터는 인터페이스에 메서드를 추가할 수 있지만 <*생각할 수 있는 모든 상황에서 불변식을 해치지 않는 디폴트 메서드를 작성하는 방법>*은 어렵다.

ex) apache commons의 SynchronizedCollection

정리해서 코드랑 설명 덧붙이기!




Item 22 : 인터페이스는 정의하는 용도로만 사용하라

  • 인터페이스는 자신을 구현한 클래스의 인스턴스를 참조할 수 있는 타입 역할
  • 클래스가 어떤 인터페이스를 구현한다는 것은 자신의 인스턴스로 무엇을 할 수 있는지를 말해주는 것

ex) 잘못 사용하는 예 ( 상수 인터페이스 - public static final 필드들만 있는 인터페이스)

상수 인터페이스
// Constant interface antipattern - do not use!
public interface PhysicalConstants {
    // Avogadro's number (1/mol)
    static final double AVOGADROS_NUMBER   = 6.022_140_857e23;

    // Boltzmann constant (J/K)
    static final double BOLTZMANN_CONSTANT = 1.380_648_52e-23;

    // Mass of the electron (kg)
    static final double ELECTRON_MASS      = 9.109_383_56e-31;
}

  클래스에서 사용하는 상수는 외부 인터페이스가 아니라 내부 구현에 해당. 아무런 의미가 없고 혼란만 준다.

  Integer.MAX_VALUE, enum type, 정적 유틸리티 클래스 등에서 사용하는게 훨씬 좋다.  좀 더 명확해진다.

유틸리티 클래스
// Constant utility class (Page 108)
public class PhysicalConstants {
  private PhysicalConstants() { }  // Prevents instantiation

  // Avogadro's number (1/mol)
  public static final double AVOGADROS_NUMBER = 6.022_140_857e23;

  // Boltzmann constant (J/K)
  public static final double BOLTZMANN_CONST  = 1.380_648_52e-23;

  // Mass of the electron (kg)
  public static final double ELECTRON_MASS    = 9.109_383_56e-31;
}




Item 23 : 태그 달린 클래스보다는 클래스 계층구조를 활용하라

  • 두가지 이상의 의미를 표현할 수 클래스의 경우 클래스 계층 구조를 활용해라.

ex) tagged class

tagged class
class Figure {
    enum Shape { RECTANGLE, CIRCLE };

    // Tag field - the shape of this figure
    final Shape shape;

    // These fields are used only if shape is RECTANGLE
    double length;
    double width;

    // This field is used only if shape is CIRCLE
    double radius;

    // Constructor for circle
    Figure(double radius) {
        shape = Shape.CIRCLE;
        this.radius = radius;
    }

    // Constructor for rectangle
    Figure(double length, double width) {
        shape = Shape.RECTANGLE;
        this.length = length;
        this.width = width;
    }

    double area() {
        switch(shape) {
            case RECTANGLE:
                return length * width;
            case CIRCLE:
                return Math.PI * (radius * radius);
            default:
                throw new AssertionError(shape);
        }
    }
}
  • 단점 : 여러 구현이 한 클래스에 혼합되어 있어 가독성도 나쁘고 사용하지 않는 필드들도 초기화 해야한다. 즉, 장황하고, 오류를 내기 쉬우며 비효율 적이다.



계층구조
abstract class Figure {
    abstract double area();
}

class Circle extends Figure {
    final double radius;

    Circle(double radius) { this.radius = radius; }

    double area() { return Math.PI * (radius * radius); }
}

class Rectangle extends Figure {
    final double length;
    final double width;

    Rectangle(double length, double width) {
        this.length = length;
        this.width = width;
    }

    double area() { return length * width; }
}
  • 간결, 명확해짐. 확장성 있는 형태로 변경
  • 태그 달린 클래스의 경우 클래스 계층을 이용해서 리팩토링하는 방법을 고민해라




Item 24 : 맴버 클래스는 되도록 static으로 만들라.

  • nested class란 클래스 안에 정의된 클래스를 말한다. nested class는 자신을 감싼 바깥 클래스에서만 쓰여야 한다.
  • 종류는 정적 멤버 클래스, 맴버 클래스, 익명 클래스, 지역 클래스 가 있다.
  • 맴버 클래스의 인스턴스 각각이 바깥 클래스의 인스턴스에 접근할 일이 없다면 무조건 static을 붙여서 정적 멤버 클래스로 만든다. 아니면 비정적으로 만든다.
  • 비정적의 경우 바깥 인스턴스에 대한 숨은 참조가 가능한데 이는 시간과 공간에 대한 비용이 들어가고 이 참좀 때문에 GC가 인스턴스를 제때 수거하지 못하는 메모리 누수가 생길 수도 있다고 한다. 



Item 25 : 톱 레벨 클래스는 한 파일에 하나만 담아라

  • 소스 파일 하나에 여러개의 톱레벨 클래스가 선언되더라도 자바 컴파일러는 문제를 삼지 않는다.
  • 하지만 컴파일러가 한 클래스에 대한 정의를 여러개 만들 수 있고 바이너리 파일이나 프로그램의 동작이 순서에 따라 달리질 수 있기 때문에 한 파일에는 하나의 톱 레벨 클래스만 담자.


ex) 프로그램 동작에 문제가 되는

main.java
public class Main {
    public static void main(String[] args) {
        System.out.println(Utensil.NAME + Dessert.NAME);
    }
}
Utensil.java
class Utensil {
    static final String NAME = "pan";
}

class Dessert {
    static final String NAME = "cake";
}
Dessert.java
// Two classes defined in one file. Don't ever do this! (Page 115)
class Utensil {
    static final String NAME = "pot";
}

class Dessert {
    static final String NAME = "pie";
}

컴파일 방식에 따른 




  • 레이블 없음