본문 바로가기

java/이펙티브 자바

Item29~30

Item23~24 진성
Item25~26 제이크
Item27~28 가을
Item29~30 워니민이
Item31~32 인호
Item33~34 민이워니

 

ITEM 29. 이왕이면 제네릭 타입으로 만들어라

 

일반클래스 -> 제네릭 클래스

클라이언트에서 직접 형변환해야하는 타입보다
제네릭 탁입이 더 안전하고 쓰기 편한다.
그러니 새로운 타입을 설계할 때는 형변환 없이도 사용할 수 있도록 해라.
그렇게 하려면 제네릭 타입으로 만들어야 할 경우가 많다.
기존 타입 중 제네릭이었어야 하는게 있다면 제네릭 타입으로 변경하자.
기존 클라이언트에는 아무 영향을 주지 않으면서, 새로운 사용자를 훨씬 편하게 해주는 길이다.

 

0. 왜 제네릭을 사용하는가?

 

자바5부터 제네릭 타입이 추가되었다.

제네릭 타입을 사용함으로 잘못된 타입이 사용될 수 있는 문제를 컴파일 과정에서 제거 할 수 있게 되었다.

 

1) 컴파일 시 강한 타입 체크를 할 수 있다.

자바 컴파일러에서 강한 타입 체크를 한다.

코드에서 잘못 사용된 타입으로 발생하는 문제점을 해결할 수 있게 되었다.

사전에 에러를 방지할 수 있다.

 

2) 타입 변환(castring)을 제거한다.

비 제네릭 코드는 집어넣을때는 괜찮음 그러나 꺼내 올때 매번 강제 형변환 해야함. => 제네릭이 안전하고 편안함

 

비 제네릭 타입 코드

List list = new ArrayList();
list.add("hello");
String str = (String) list.get(0); // 타입 변환

 

제네릭 타입 코드

List list<List> = new ArrayList<List>(); //제네릭 타입
list.add("hello");
String str = =list.get(0); // 타입 변환 안해도 됨

 

 

 

1. 클래스 선언 : 타입 매개변수 추가

구체적인 타입을 지정해야한다.

 

비 제레닉 Box 클래스

public class Box {
    private  Object object;
 
    public void set(Object object)
    {this.object = object;}
    
    public Object get()
    {return object;}
}

Object 클래스는 모든 자바 클래스의 최상위 조상 클래스이다. 따라서 자식 객체는 부모 타입에 대입할 수 있다는 성질로 인해 모든 자바 객체는 Object타입으로 자동 타입 변환되어 저장된다.

Object로 받아버리면 모든 종류의 자바 객체를 저장할 수 이는 장점이 있다.

 

 

비제네릭 사용 -> 타입변환이 발생한다.

// 비 제네릭
Box box = new Box();
box.set("홍길동");                // String 타입을 Object 타입으로 자동 타입 변환해서 저장
String name = (String) box.get(); // Object 타입을 String 타입으로 강제 타입 변환해서 얻음

box.set(new Apple());                // Apple 타입을 Object 타입으로 자동 타입 변환해서 저장
Apple apple = (Apple) box.get(); // Object 타입을 Apple 타입으로 강제 타입 변환해서 얻음

그러나 저장할 때 타입 변환이 발생하고, 읽어올때도 타입변환이 발생한다.

이러한 타입 변환이 빈번해지면 전체 프로그램 성능에 좋지 못한 결과를 가져온다.

 

타입변환이 일어나지 않도록 하는 방법이 없을까?

 

제레닉 Box 클래스

public class Box<T> {
   private T t;
   public T get() { return t };
   public void set(T t) { this.t = t; }
}

 

제네릭 사용 -> 타입변환이 발생하지 않는다.

// 제네릭 코드
Box<String> box = new Box<String>();  // 타입 파라미터를 명시적으로 타입 지정
box.set("홍길동");
String str = box.get(); // 형변환 안함


Box<Inteager> box2 = new Box<Inteager>(); // 타입 파라미터를 명시적으로 타입 지정
box2.set(1004);
int value = box2.get(); // 형변환 안함

필드 타입이 String으로 변경되었고 set()메소드도 get() 메소드도 String 타입으로 리턴하도록 변경되었다.

그래서 저장하고 읽어올때 전혀 타입 변환이 발생하지 않는다.

 

이와 같이 제네릭은 클래스를 설계할 때 구체적인 타입을 명시하지 않고, 

실제 클래스가 사용될 때 구체적인 타입을 지정함으로써 타입 변환을 최소화 시킨다.

 

 

 

비 제네릭 : Stack.class

public class Stack {
    private Object[] elements;
    private int 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);
    }
}

 

제네릭: Stack.class --> 컴파일이 되지 않는다.

public class Stack<E> {
    private E[] elements;
    private int size = 0;
    private static final int DEFAULT_INITIAL_CAPACITY = 16;

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

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

    public E pop() {
        if (size == 0)
            throw new EmptyStackException();
        E result = elements[--size];
        elements[size] = null; // 다 쓴 참조 해제 
        return result;
    }
    ... // isEmpty와 ensureCapacity 메서드는 그대로다. 
}

 

2. 배열은 실체 불가 타입으로 만들수 없다.

 

제네릭 배열 생성 오류

오류 내용
Stack.java:8: generic array creation elements =new E[DEFAULT_INITIAL_CAPACITY];

위의 코드에서는 하나의 오류가 발생한다. E와같은 실체화 불가 타입으로는 배열을 만들 수 없다.

 

제네릭 배열을 생성하기 위한 첫 번째 우회방법

Object 배열을 생성한 후 제네릭 배열로 형 변환한다. 대놓고 우회하는 방법이다.

 

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

(E[ ]) new Object[DEFAULT_INITIAL_CAPACITY]; 로 하여 Object 배열로 생성한다.

배열에 저장되는 원소 타입은 항상 E 다 . 따라서 형변환은 확실히 안전한다.

안전해서 경고를 숨긴다. @SuppressWarnings("unchecked")

 

배열 제네릭
런타임시 자신이 담기로한 원소의 타입인지 인지하고 확인한다. 런타임시 타입 정보를 지움.. 그래서 아무거나옴

 

제네릭 배열을 생성하기 위한 두 번째 우회방법

elements 필드의 타입을 E[]에서 Object[]로 변경한다.

이때도 마찬가지로 경고가 발생한다. 첫 번째 방법처럼 우리가 스스로 검증하고 경고를 제거해야 한다.

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

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

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

    public E pop() {
        if (size == 0) {
            throw new EmptyStackException();
        }
        @SuppressWarnings("unchecked") E result = (E) elements[--size];
        elements[size] = null;
        return result;
    }

elements 필드를 Object[]로 변경하는 방법은 힙 오염이 일어나지 않는다는 장점이 있다.

하지만 원소를 pop 할 때마다 형 변환을 해주어야 한다는 면에서 단점이라 볼 수도 있다.

 

 

3. 배열 생성 코드를 제네릭으로 사용하는 법

 

 

 

4. 제네릭 타입은 타입 매개변수에 아무런 제약을 두지 않는다.

 

 

 

ITEM 30. 이왕이면 제네릭 메서드로 만들어라

메서드도 제네릭으로 만들어라

제네릭 타입과 마찬가지로 
클라이언트에서 입력한 매개변수와 반환값을 명시적으로 형변환 해야하는 메서드보다
제네릭 메서드가 더 안전하며 사용하기 쉽다.
타입과 마찬가지로 메소드도 형변환 없이 사용할 수 있는 편이 좋으며,
많은 경우 그렇게 하려면 제너릭 메서드가 되어야한다.
역시 타입과 마찬가지로 형 변환을 해줘야하는 기존 메서드는 제네릭하게 만들자.
기존 클라이언트는 그대로 둔 채 새로운 사용자의 삶을 훨씬 편하게 만들어줄 것이다.

 

 

1. 타입 파라미터를 구체적으로 써라

 

 

2. 정적 팩터리

제네릭은 런타임에 타입 정보가 소거되므로

하나의 객체를 어떤 타입으로든 매개변수화 할 수 있다.

하지만 이렇게 하려면 요청한 타입 매개변수에 맞게 매번 그 객체의 타입으로 바꿔줘야한다.

 

정적 팩터리 패턴은 제네릭 싱글턴 팩터리라 하며

Collections.reverseOrder 함수 객체나

Collections.emptySet 같은 컬렉션용으로 사용한다.

 

 

3. 항등함수를 담은 클래스

항등함수: 입력 값을 수정 없이 그대로 반혼하는 특별한 함수.

항등함수 객체는 상태가 없다. 요청할 때마다 새로 생성하는 것은 낭비다.

 

형변환하면 형변환 경고가 발생한다. @SuppressWarning 애너테이션을 추가 -> 컴파일 오류 발생하지 않음

 

 

4. 매개변수의 허용 범위를 한정하기

재귀적 타입 한정: 자기자신이 들어간 표현식을 사용하여 매개변수의 허용 범위를 한정한다.

 

Comparable 인터페이스와 함께 쓰인다.

재귀적 타입 한정을 이용해 상호 비교할 수 있다.

 

<E extends Comparable<E>> 모든 타입 E는 자신과 비교할 수 있다.

 

'java > 이펙티브 자바' 카테고리의 다른 글

Item51~Item52  (0) 2022.01.23
item 43~46  (0) 2022.01.16
[item 21~22]  (0) 2022.01.01
[item 1] 생성자 대신 정적 팩터리 메서드를 고려하라  (0) 2021.12.29
[item 17~18]  (0) 2021.12.29