JAVA

Generic(제네릭)

bergerac 2025. 3. 15. 09:11
  • 제네릭은 데이터 타입을 데이터 타입을 일반화(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