- 제네릭은 데이터 타입을 데이터 타입을 일반화(Generalization)하여, 여러 타입을 처리할 수 있도록 하는 기능
- 컴파일 시 타입을 체크할 수 있도록 도와준다.
- 특정 타입에 의존하지 않고 다양한 타입을 지원하는 설계가 가능하다.
- 주요장점 : 타입 안정성, 코드안정성, 코드 재사용성, 가독성
1. 제네릭 사용 방법
class 클래스명<제네릭타입> {
- 제네릭 타입을 사용하여 필드, 생성자, 메서드 정의
}
class Pair<K, V> { // K: Key, V: Value
private K key;
private V value;
public Pair(K key, V value) {
this.key = key;
this.value = value;
}
public K getKey() { return key; }
public V getValue() { return value; }
}
1) 제네릭 클래스
타입변수 :주로 <T> 형태로 사용 (Type: 타입), 이후에는 다음 알파벳값을 주로 사용(U, V 등)
- 해당 기능 사용 시 작성되는 실질적인 타입이 타입 변수에 전달되어 사용
클래스명<제네릭 타입> 인스턴스명 = new 클래스명<>();
제네릭 타입 명시는 필수적인 것은 아니며 제네릭 타입의 구조에 값이 들어올 경우 해당 값으로 타입을 자동유추할 수 있다. <>안에 타입변수를 선언하며 T(Type), K(Key), V(Value) 등 의미 있는 알파벳을 사용합니다.
Pair<Integer, String> pair1 = new Pair<>(123, "안녕하세요"); // 인자값으로 타입을 자동 유추
Pair<Integer, String> pair2 = new Pair<Integer, String>(123, "안녕하세요");
// 좌항의 클래스타입에는 타입 명시
**기본형 데이터는 Wrapper 클래스의 사용을 필수로 해야한다.
2) 제네릭 메서드
[접근제한자] <제네릭 타입> 반환타입 메서드명(매개변수...) { 구현부 }
메서드 호출 시 전달된 타입에 따라 메서드 내부의 타입변수의 값이 결정된다.
static <T> void print(T value) {
System.out.println(value);
}
<사용법>
Pair.<String>print("안녕"); // 안녕
Pair.<Integer>print(500); // 500
- Pair.<String>print("안녕") : T가 String으로 결정
- Pair.<Integer>print(500) : T가 Integer로 결정
!! 타입을 잘못 지정하면 컴파일에러가 발생할 수 있다.
2. 제네릭 - 와일드 카드(WildCard)
와일드 카드란?
제네릭 타입을 사용할 때 정확한 타입을 모를 대, 혹은 유연한 타입 처리를 위해 사용됩니다.
기호: ?
줄 상속관계가 있는 여러 타입을 수용할 때 사용한다.
1) 일반 와일드카드<?>
모든 타입을 수용할 수 있습니다. 읽기(Read)는 가능하지만 쓰기(Write)는 제한됩니다.
==> 어떤 값이든 받아올 수는 있지만, 값 추가는 null만 허용한다.
List<?> list = new ArrayList<>();
list.add(null); // 가능 (null은 어떤 타입이든 수용)
list.add("Hello"); // ❌ 오류! (구체적인 타입을 모르기 때문에 안전하지 않음)
list.add(123); // ❌ 오류!
2) 상한 제한 와일드카드<? extends Type>
Type과 그 하위타입만 허용한다(자식 타입까지만 허용한다)
주로 읽기전용으로 사용한다. 즉 안전하게 읽을 수는 있지만 구체적인 값 추가는 불가능하다.
List<? extends Number> list = new ArrayList<Integer>(); // Integer는 Number의 하위 타입
// list.add(123); // ❌ 구체적인 값 추가 불가 (타입 안전성 문제)
즉 읽기는 가능하고 쓰기는 제한된다. 주로 하위 타입까지 모두 처리하고 싶을 때 사용한다.
3) 하한 제한 와일드카드<? super Type>
Type 과 그 상위타입만 허용한다. 즉 부모타입까지만 허용한다.
구체적인 값 추가가 가능하나 읽을 때는 Object로 받아야 한다.
List<? super Integer> list = new ArrayList<Number>(); // Number는 Integer의 상위 타입
list.add(123); // ✅ 가능
list.add(456); // ✅ 가능
// list.add(3.14); // ❌ 실수는 Integer의 상위 타입 아님
쓰기가 가능하고 읽기 시 Object로 반환한다. 주로 상위타입까지 모두 처리하고 싶을 때 사용한다.
4) 명시적 형변환(캐스팅)이 필요한 이유
와일드카드로 선언된 객체에서 값을 꺼낼 때 구체적인 타입을 모르기 때문에 Object로 반환되며 필요한 경우 명시적 형변환이 필요합니다.
Message<?> message = new Message<>();
Object obj = message.getMessage(); // 안전
String str = (String) message.getMessage(); // 명시적 형변환 (실제 타입이 String일 때만 안전)
<예 제>
class Basket<T> {
private T item;
public void setItem(T item) { this.item = item; }
public T getItem() { return item; }
}
Basket<?> basket = new Basket<String>();
// basket.setItem("사과"); // ❌ 타입을 몰라서 추가 불가
basket.setItem(null); // ✅ null만 허용
'JAVA' 카테고리의 다른 글
Builder Pattern(빌더 패턴) (0) | 2025.03.17 |
---|---|
Singleton Pattern(싱글톤 패턴) (0) | 2025.03.16 |
MVC 패턴 (0) | 2025.03.14 |
JAVA의 예외처리 (Throw/Throws) (1) | 2025.03.13 |
JAVA 예외처리(try- catch 블록 ) (1) | 2025.03.12 |