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 |