Page tree

Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

Code Block
languagejava
themeEclipse
title상속의 잘못된 예
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());
    }

}

반환 예상 값 : 3

실제 출력 값 :  6

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

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

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

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


  • 안전한 방법은? 

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

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

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

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

Code Block
languagejava
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이라고 pattern이라고 한다.


  • 상속은 is-a 관계를 만족하는 두개의 클래스에서만 구현해야 한다. (ex. A와 B 클래스 : B는 A인가? 를 만족)
  • Java에서 이 원칙을 위반한 클래스는 Stack 과 Vector, Properties 와 HashTable 이 있다고 한다.

...

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

  • 인터페이스와 추상클래스의 가장 큰 차이는 추상클래스를 구현한 하위클래스는 반드시 추상클래스의 하위
    타입이
    타입되어야한다는 것
  • 인터페이스는 믹스인(mixin)
    정의에
    정의적합하다.
    • mixin : 클래스가 구현할 수 있는 타입, 원래의 primary type 외에 특정 부가적인 행위를 제공하고 선언하는 효과를 준다(?)

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

...

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

참고자료 

...

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

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

ex) apache commons의 SynchronizedCollection정리해서 코드랑 설명 덧붙이기!SynchronizedCollection 

  • 기존 인터페이스에 디폴트 메서드로 새 메서드를 추가하는 일은 꼭 필요한 경우가 아니라면 피하도록 하자. 


...

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

...

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

...

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

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

Code Block
languagejava
titlemain.java
public class Main {
    public static void main(String[] args) {
        System.out.println(Utensil.NAME + Dessert.NAME);
    }
}

...

Code Block
languagejava
titleDessert.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";
}

컴파일 방식에 따른