TIL
  • Contents
  • Book
    • 도메인 주도 설계
      • 1. 동작하는 도메인 모델 만들기
    • 오브젝트
      • 데이터 중심 설계
      • 책임 중심 설계
      • 책임 할당을 위한 GRASP 패턴
      • 메시지와 인터페이스
      • 객체 분해
    • Effective Java
      • Item 7 - 다 쓴 객체 참조를 해제하라
      • Item 7 발표 내용
      • Item 13 - clone 재정의는 주의해서 진행하라
      • Item 13 발표 내용
      • Item 16 - public 클래스에서는 public 필드가 아닌 접근자 메서드를 사용하라
      • Item 16 발표 내용
      • Item 26 - 로 타입은 사용하지 말라
      • Item 28 - 배열보다는 리스트를 사용하라
      • Item 28 발표 내용
      • Item 29 - 이왕이면 제네릭 타입으로 만들라
      • Item 30 - 이왕이면 제네릭 메서드로 만들라
      • Item 31 - 한정적 와일드 카드를 사용해 API 유연성을 높이라
      • Item 35 - ordinal 메서드 대신 인스턴스 필드를 사용하라
      • Item 37 - ordinal 인덱싱 대신 EnumMap을 사용하라
      • Item 37 발표 내용
      • Item 43 - 람다보다는 메서드 참조를 사용하라
      • Item 43 발표 정리
      • Item 56 - 공개된 API 요소에는 항상 문서화 주석을 작성하라
      • Item 56 발표 정리
      • Item 62 - 다른 타입이 적절하다면 문자열 사용을 피하라
      • Item 62 발표 정리
      • Item 73 - 추상화 수준에 맞는 예외를 던지라
      • Item 83 - 지연 초기화는 신중히 사용하라
      • Item 83 발표 내용
      • Item 89 - 인스턴스 수를 통제해야 한다면 readResolve보다는 열거 타입을 사용하라
      • Item 89 발표 내용
    • 개발자를 위한 SQL 튜닝
      • SQL 쿼리 실습을 위한 DB 서버 구축
      • 인덱스 튜닝
      • 인덱스 스캔 튜닝
      • 인덱스 스캔 튜닝 실습
      • 인덱스 패스트 풀 스캔
      • 테이블 풀 스캔 튜닝
      • 조인 튜닝
      • 중첩 루프 조인 튜닝
      • 중첩 루프 조인 튜닝 실습
      • 해시 조인 튜닝
      • 해시 조인 튜닝 실습
      • 세미 조인 튜닝
      • 세미 조인 튜닝 실습
      • 아우터 조인
      • 함수 튜닝
      • 부분 범위 처리 튜닝
      • 파티셔닝 튜닝
      • 파티션 인덱스 튜닝
      • 병렬 처리 튜닝
  • Java
    • Design Pattern
      • Intro
      • Types of Design Patterns
      • Creational
        • Builder Pattern
        • Singleton Pattern
        • Prototype Pattern
        • Factory Pattern
        • Abstract Factory Pattern
      • Structural
        • Adapter Pattern
        • Bridge Pattern
        • Composite Pattern
        • Decorator Pattern
        • Facade Pattern
        • Flyweight Pattern
        • Proxy Pattern
      • Behavioural
        • Chain of Responsibility Pattern
        • Command Pattern
        • Interpreter Pattern
        • Iterator Pattern
        • Mediator Pattern
        • Memento Pattern
        • Observer Pattern
        • State Pattern
        • Strategy Pattern
        • Template Method Pattern
        • Visitor Pattern
    • Java
      • Cracking the Coding Interview
      • TDD, Clean Code with Java 11기
        • 자동차 레이싱
        • 로또
        • 사다리 타기
        • 볼링 게임 점수판
    • 궁금증
      • 자바 8 버전의 인터페이스와 추상클래스
      • 자바의 제네릭은 어떻게 이전 버전과 호환되는 걸까?
      • 스프링 MVC 기본 구조
      • 마샬링과 직렬화
      • 인터뷰 질문 모음
      • Code Coverage
  • Database
    • Database
      • SQL 레벨업
      • DB 스터디
        • DBMS
          • MySQL
        • INDEX
        • Join(Nested Loop, Hash)
        • Join(Semi, Outer)
        • Partial Range Processing
        • Function
        • Partitioning
        • Parallel Processing
  • Network
  • Architecture
    • Issue
      • Git Push Error
      • SonarLint Warning - assertThatExceptionOfType()
  • Infra
  • Spring
    • Spring JPA
      • 1. 데이터 모델링 및 연관관계 설정
      • 2. 최적화 내용
      • 3. Spring-Data-Jpa
      • 4. Query DSL
    • Spring Security
      • Intro
    • Spring Batch
      • 배치용 디비 설치
      • 배치 데이터 분석하기
      • 배치 프로세스 구상하기 및 성능 차이 확인하기
  • Issue
  • Tistory
    • Tistory Blog
  • Design High Performing Architectures
  • Design Resilient Architectures
  • Design Secure Applications And Architectures
  • Design Cost-Optimized Architectures
Powered by GitBook
On this page
  • Intro
  • 제네릭이 필요한 Object 기반 스택
  • 배열을 사용하는 코드를 제네릭으로 만들 때 문제가 발생하게 되고, 적절한 해결책은 두 가지가 있다.
  • 제네릭 Stack 을 사용하는 예시
  • 핵심 정리

Was this helpful?

  1. Book
  2. Effective Java

Item 29 - 이왕이면 제네릭 타입으로 만들라

이왕이면 제네릭 타입으로 만들라

Intro

  • JDK가 제공하는 제네릭 타입과 메서드를 사용하는 일은 일반적으로 쉬운 편이지만, 제네릭 타입을 새로 만드는 일은 조금 더 어렵다.

제네릭이 필요한 Object 기반 스택

  • 해당 클래스는 원래 제네릭 타입이어야 한다.

    • 클라이언트는 스택에서 꺼낸 객체를 형변환해야 하는데, 이때 런타임 오류가 날 위험이 있다.

public class Stack {
    private Object[] elements;
    private mnt size = 0;
    private static final int DEFAULT_INITIAL_CAPACITY = 16;

    public Stack() {
        elements = new Object[DEFAULT_INITIAL_CAPACITY];
    }

    public void push(Object e) {
        ensureCapacity();
        elements[size++] = e;
    }

    public Object pop() {
        if (size == 0)
            throw new EmptyStackException();
        Object result = elements[-size];
        elements[size] = null; // 다쓴참조해제 
        return result;
    }

    public boolean isEmpty() {
        return size = 0;
    }

    private void ensureCapacity() {
        if (elements.length == size)
            elements = Arrays.copyOf(elements, 2 * size + 1);
    }
}
  • 일반 클래스를 제네릭 클래스로 만드는 첫 단계는 클래스 선언에 타입 매개변수를 추가하는 일이다.

    • 스택이 담을 원소의 타입 하나만 추가하면 된다.

    • E와 같은 실체화 불가 타입으로는 배열을 만들 수 없다.

// 컴파일 되지 않는 코드 클래스에 <E> 추가 
public class Stack<E> {
    // Object[] -> E[]
    private E[] elements;
    private int size = 0;
    private static final int DEFAULT_INITIAL_CAPACITY = 16;

    public Stack() {
        // Object -> E
        elements = new E[DEFAULT_INITIAL_CAPACITY];
    }

    // Object -> E
    public void push(E e) {
        ensureCapacity();
        elements[size++] = e;
    }

    // Object -> E
    public E pop() {
        if (size = 0)
            throw new EmptyStackException();

        // Object -> E
        E result = elements[-size];
        elements[size] = null; //다쓴참조해제 
        return result;
    }
    // isEmpty와ensureCapacity메서드는그대로다. 
}

배열을 사용하는 코드를 제네릭으로 만들 때 문제가 발생하게 되고, 적절한 해결책은 두 가지가 있다.

첫 번째는 제네릭 배열 생성을 금지하는 제약을 대놓고 우회하는 방법

  • Object 배열을 생성한 다음 제네릭 배열로 형변환 한다.

  • 컴파일러는 오류 대신 경고를 내보낼 것이다.

  • 타입 안전하지 않다는 경고

  • 비검사 형변환이 프로그램의 타입 안전성을 해치지 않음 스스로 확인해야 한다.

    • 생성자가 비검사 배열 생성 말고는 하는 일이 없으니 생성자 전체에서 경고를 숨겨도 좋다.

  • 어노테이션을 달면 Stack을 깔끔하게 컴파일 되고, 명시적으로 형변환하지 않아도 ClassCastException 걱정 없이 사용할 수 있게 된다.

// 배열을 사용한 코드를 제네릭으로 만드는 방법 1
public class Stack<E> {
    // ...

    // 배열 elements 는 push(E)로 넘어온 E 인스턴스만 담는다.
    // 따라서 타입 안전성을 보장하지만, 이 배열의 런타임 타입은 E[]가 아닌 Object[]이다.
    @SuppressWarnings("unchecked")
    public Stack() {
        elements = (E[]) new Object[DEFAULT_INITIAL_CAPACITY];
    }
    // ...
}

두 번째는 elements 필드의 타입을 E[]에서 Object[]로 바꾸는 방법

  • 첫 번째와는 다른 오류가 발생한다.

  • 배열이 반환한 원소를 E로 형변환하면 오류 대신 경고가 뜬다.

  • E는 실체화 불가 타입이므로 컴파일러는 런타임에 이뤄지는 형변환이 안전한지 증명할 방법이 없다.

  • pop 메서드 전체에서 경고를 숨기지 말고, 비검사 형변환을 수행하는 할당문에서만 숨긴다.

// 배열을 사용한 코드를 제네릭으로 만드는 방법 2
public class Stack<E> {
    // 비검사 경고를 적절히 숨긴다.
    public E pop() {
        if (size == 0)
            throw new EmptyStackException();

        // push에서 E 타입만 허용하므로 이 형변환은 안전하다.
        @SuppressWarnings("unchecked")
        E result = (E) elements[--size];

        elements[size] = null;
        return result;
    }
}

제네릭 배열 생성을 제거하는 두 방법 모두 나름의 지지를 얻고 있다.

  • 첫 번째 방법은 가독성이 더 좋다.

    • 배열의 타입을 E[]로 선언하여 오직 E 타입 인스턴스만 받음을 확실히 어필한다.

    • 형변환을 배열 생성 시 단 한번만 해주면 된다.

  • 두 번째 방식에서는 배열에서 원소를 읽을 때마다 해줘야 한다.

    • 따라서 현업에서는 첫 번째 방식을 더 선호하며 자주 사용한다.

    • 힙 오염이 마음에 걸리는 프로그래머는 두 번째 방식을 고수하기도 한다.

제네릭 Stack 을 사용하는 예시

  • 명령줄 인수들을 역순으로 바꿔 대문자로 출력하는 프로그램

    • Stack 에서 꺼낸 원소에서 String의 toUpperCase 메서드를 호출할 때 명시적 형변환을 수행하지 않는다.

      (컴파일러에 의해 자동 생성)

    • 형변환이 항상 성공함을 보장한다.

public class Stack<T> {
    public static void main(String[] args) {
        Stack<String> stack = new Stack<>();
        for (String arg : args) {
            stack.push(arg);
        }
        while (!stack.isEmpty()) {
            System.out.println(stack.pop().toUpperCase());
        }
    }
}
  • 사실 제네릭 타입 안에서 리스트를 사용하는 게 항상 가능하지도, 꼭 더 좋은 것도 아니다.

  • 자바가 리스트를 기본 타입으로 제공하지 않으므로 ArrayList 같은 제네릭 타입도 결국 기본 타입인 배열을 사용해 구현해야 한다.

  • 또한 HashMap 같은 제네릭 타입은 성능을 높일 목적으로 배열을 사용하기도 한다.

  • Stack 예처럼 대다수의 제네릭 타입은 타입 매개변수에 아무런 제약을 두지 않는다.

    • Stack, Stack, Stack>, Stack 등 어떤 참조 타입으로도 Stack을 만들 수 있다.

    • 단, 기본 타입은 사용할 수 없다.

    • Stack나 Stack을 만들려고 하면 컴파일 오류가 난다.

  • 타입 매개변수에 제약을 두는 제네릭 타입도 있다.

    • java.util.current.DelayQueue

    • 타입 매개변수 목록인 는 java.util.concurrent.Delayed 의 하위 타입만 받는다는 것이다.

    • DelayQueue 자신과 DelayQueue를 사용하는 클라이언트는 DelayQueue의 원소에서 곧바로 Delayed 클래스의 메서드를 호출할 수 있다.

    • ClassCastException 걱정은 할 필요가 없다.

    • 이러한 타입 매개변수 E를 한정적 타입 매개변수(bounded type parameter)라 한다.

    • 모든 타입은 자기 자신의 하위 타입이므로 DelayQueue로도 사용할 수 있음을 기억해야 한다.

import java.util.concurrent.BlockingQueue;

public class DelayQueue<E extends Delayed> implements BlockingQueue<E> {

}

핵심 정리

  • 클라이언트에서 직접 형변환해야 하는 타입보다 제네릭 타입이 더 안전하고 쓰기 편하다.

    • 새로운 타입을 설계할 때는 형변환 없이도 사용할 수 있도록 하라.

    • 그런 경우 제네릭 타입으로 만들어야 하는 경우가 많다.

    • 기존 타입 중 제네릭이었어야 하는 부분이 있다면 제네릭 타입으로 변경하자.

PreviousItem 28 발표 내용NextItem 30 - 이왕이면 제네릭 메서드로 만들라

Last updated 3 years ago

Was this helpful?

이때 타입 이름으로는 보통 E를 사용한다.

비검사 형변환이 안전함을 직접 증명했다면 범위를 최소로 좁혀 @SuppressWarnings 어노테이션으로 해당 경고를 숨긴다.

하지만 배열의 런타임 타입이 컴파일 타임 타입과 달라 힙 오염(heap pollution)을 일으킨다.

"배열보다는 리스트를 우선하라"는 과 모순되어 보인다.

이는 자바 제네릭 타입 시스템의 근본적인 문제이나, 박싱된 기본타입을 사용해 우회할 수 있다.

기존 클라이언트에는 아무런 영향을 주지 않으면서, 새로운 사용자를 훨씬 편하게 해주는 길이다.

아이템 68
아이템 27
아이템 32
아이템 28
아이템 61
아이템 26