1. 제네릭(generics)
1) 정의
- 컴파일 시 타입을 체크해주는 기능(compile-time type check) (JDK1.5부터)
- 클래스 내부에서 사용할 데이터 타입을 외부에서 지정하는 기법
2) 장점
- 객체 타입을 컴파일 시 체크할 수 있으므로 런타임 에러를 줄일 수 있음 👉🏻 안정성을 높임
- 저장된 객체를 꺼낼 때 형변환이 필요 없음
- (위 두 가지 장점으로 인하여) 타입체크와 형변환 생략이 가능하므로 코드가 간결해짐
2. 제네릭의 용어
1) 제네릭 클래스, T의 Box 또는 T Box라고 읽음
class Box<T> // 지네릭 클래스 T의 Box 선언 또는 지네릭 클래스 T Box 선언
2) 원시 타입(raw type)
class Box<T> // Box가 원시 타입
3) 다이아몬드 연산자
- '<>' 꺽쇠 괄호 키워드를 다이아몬드 연산자라고 함
4) 타입 변수(type variable) 또는 타입 매개 변수(T는 타입 문자)
- 클래스를 작성할 때 Object타입 대신 타입 변수<T>를 선언해서 사용
- 다이아몬드 연산자 안에 식별자 기호를 지정하는 것
- 일반적으로 Type의 첫 글자인 T를 사용
- ArrayList<E>의 경우 일반적으로 요소(Element)의 첫 글자인 E를 사용
ArrayList<E> arrayList = new ArrayList<E>();
Box<T> box = new Box<T>();
5) 대입된 타입(parameterized type)
- 객체 생성 시 타입 변수(E)대신 실제 타입을 대입한 것
- 위치: 참조변수와 생성자
// 타입 변수 E 대신 실제 타입 Tv 대입 -> Tv는 대입된 타입(=매개변수화된 타입)
ArrayList<Tv> tvList = new ArrayList<Tv>();
// 타입 변수 T 대신 실제 타입 String 대입 -> String은 대입된 타입(=매개변수화된 타입)
Box<String> box = new Box<String>();
- 타입 변수 대신 실제 타입이 지정되면 형변환 생략 가능
- 컴파일 후에는 원시 타입(예시에서 Array와 Box) 으로 바뀜
3. 제네릭 타입과 다형성
1) 제네릭 타입 간 다형성 성립 불가
- 참조 변수에 지정한 지네릭 타입과 생성자에 지정한 지네릭 타입은 일치해야 함
ArrayList<Tv> list = new ArrayList<Tv>(); // OK.참조변수 타입 Tv와 생성자의 지네릭 타입 Tv 일치
ArrayList<Product> list = new ArrayList<Tv>(); // ERROR. 참조변수 타입 Product와 생성자의 지네릭 타입 Tv 불일치
2) 제네릭 클래스 간 다형성 성립
List<Tv> list = new ArrayList<Tv>(); // OK.다형성, ArrayList가 list 구현
List<Tv> list = new LinkedList<Tv>(); // OK.다형성, LinkedList가 list 구현
3) 매개변수의 다형성 성립
- 제네릭 클래스에 특정 제네릭 타입만 저장하는 방법
- 특정 제네릭 타입의 지네릭 클래스를 생성하고, 이 클래스에 특정 제네릭 타입의 자손 객체를 저장
👉🏻 (Product타입 ArrayList를 생성하고, ArrayListd Product타입의 자손인 Tv와, Audio 객체를 저장)
- 단, 제네릭 클래스에서 저장된 객체를 꺼낼 때 형변환 필요
ArrayList<Product> list = new ArrayList<product>(); // 지네릭 타입 Product 일치, 지네릭 클래스 ArrayList일치
list.add(new Product());
list.add(new Tv());
list.add(new Audio());
// 꺼낼 때 형변환 필요
Product product = list.get(0);
Tv tv = (Tv)list.get(1);
4. Iterator<E>
- Iterator에도 제네릭이 적용되어 있음
- 클래스를 작성할 때 Object타입 대신 T와 같은 타입 변수를 사용
public interface Iterator<E> {
boolean hasNext();
E next();
void remove();
}
5. HashMap<K,V>
- 여러 개의 타입 변수가 필요한 경우 콤마 ','를 구분자로 선언(두 개 이상 가능)
- K, V는 Key와 Value의 첫 글자를 딴 임의의 참조형 타입
6. 제한된 제네릭 클래스(타입 매개 변수T의 종류 제한하기)
- extends로 대입할 수 있는 타입 제한
class FruitBox<T extends Fruit> { // Fruit의 자손만 타입으로 제한
ArrayList<T> list = new ArrayList<T>();
}
- 인터페이스인 경우에도 extends사용(implements 사용X , 상속과 인터페이스 제한 둘 다 하는 경우 콤마','를 사용하지 않고 '&'을 사용)
class FruitBox<T extends Fruit & Eatable> extends Box<T> {}
7. 제네릭의 제약
- 타입변수에 대입은 인스턴스 별로 다르게 가능
Box<Apple> appleBox = new Box<Appple>();
Box<Grape> grapeBox = new Box<Grape>();
- static 멤버에 타입 변수 사용 불가
class Box<T> {
static T item; // 에러
static int compare(T t1, T t2) {...} // 에러
}
- 배열 생성 시 타입 변수 사용 불가, 타입 변수로 배열 선언은 가능
8. 와일드 카드 <?>
- 하나의 참조 변수로 대입된 타입이 다른 객체 참조 가능
- 지네릭 타입에 와일드 카드 사용시 여러 타입 대입 가능하지만 <? extends T & E>와 같이 '&' 사용은 불가
<? extends T> // 와일드 카드 상한 제한, T와 자손들만 가능
<? super T> // 와일드 카드 하한 제한, T와 조상들만 가능
<?> // 제한없음, 모든 타입 가능
9. 제네릭 메서드
- 제네릭 타입이 선언된 메서드(타입 변수는 메서드 내에서만 유효)
static <T> void sort(List<T> list, Comparator<? super T> c)
- 클래스의 타입 매개변수<T>와 메서드의 타입 매개변수 <T>는 별개
- 메서드 호출 시 타입을 대입(대부분 생략 가능)
Fruit<Fruit> fruitBox = new FruitBox<Fruit>();
FruitBox<Apple> appleBox = new FruitBox<Apple>();
// ...
System.out.println(Juicer.<Fruit>makeJuice(fruitBox)); // <Fruit> 생략 가능
System.out.println(Juicer.<Apple>makeJuice(appleBox)); // <Apple> 생략 가능
- 메서드 호출할 때 타입을 생략하지 않을 경우 클래스명 생략 불가(드문 경우)
System.out.println(<Fruit>makeJuice(fruitBox)); // Error. 클래스명 생략 불가
System.out.println(this.<Fruit>makeJuice(fruitBox)); // OK. 클래스명 대신 this 사용
System.out.println(Juicer.<Fruit>makeJuice(fruitBox)); // OK. 클래스명 존재
- 지네릭 메서드와 와일드카드
// 지네릭 메서드
class Juicer {
static <T extends Fruit> Juice makeJuice(FruitBox<T> box) { // 지네릭 메서드는 메서드를 호출할 때마다 다른 지네릭 타입을 대입할 수 있게 한 것
String tmp = "";
for(Fruit2 f : box.getList())
tmp += f + " ";
return new Juice(tmp);
}
}
// 와일드 카드
class Juicer {
static Juice makeJuice(FruitBox<? extends Fruit> box) { // 와일드 카드는 하나의 참조변수로 서로 다른 타입이 대입된 여러 지네릭 객체를 다루기 위한 것
String tmp = "";
for(Fruit2 f : box.getList())
tmp += f + " ";
return new Juice(tmp);
}
}
10. 제네릭 타입의 형변환
- 제네릭 타입과 원시 타입 간 형변환은 바람직하지 않음(가능하지만 경고 발생)
Box<Object> objBox = null;
Box box = (Box)objBox; // OK, 경고 발생. 지네릭타입 Box<Object>인 objBox -> 원시타입 Box로 형변환
objBox = (Box<Object>)box; // OK, 경고 발생. 원시타입 Box인 box를 -> 지네릭타입 Box<Object>로 형변환
* 원시타입으로 쓰는게 가능은 하나 제네릭형식으로 사용하는 것을 권장함
- 와일드 카드가 사용된 제네릭 타입으로는 형변환 가능
Box<Object> objBox = (Box<Object>)new Box<String>(); // Error. 형변환 불가능
Box<? extends Object> wbox = (Box<? extends Object>)new Box<String>(); // OK
Box<? extends Object> wBox = new Box<String>(); // 위 문장과 동일, 형변환 생략한 문장
- <? extends Object>를 줄여서 <?>로 사용 가능
11. 제네릭 타입의 제거
1) 컴파일러는 지네릭 타입의 경계(bound) 제거
class Box<T extends Fruit> {
void add(T t) {
// ...
}
}
// 컴파일러가 지네릭 타입을 제거
class Box {
void add(Fruit t) {
// ...
}
}
2) (제네릭 타입 제거 후) 타입 불일치 시 형변환 추가
T get (int i) {
return list.get(i);
}
// 형변환 추가
Fruit get (int i) {
return (Fruit)list.get(i);
}
3) 와일드 카드가 포함된 경우, 적절한 타입으로 형변환 추가
'Java' 카테고리의 다른 글
Java 애너테이션(annotation)이란? (0) | 2023.01.17 |
---|---|
Java 열거형(enum) (0) | 2023.01.17 |
Java Collections 클래스 (0) | 2023.01.09 |
Java HashMap, Hashtable (0) | 2023.01.07 |
Java 컬렉션(Collection) - Set(집합) HashSet, TreeSet (0) | 2023.01.03 |