cTosMaster 2025. 3. 14. 16:57

1. Generics 란? 

 

제네릭스(Generics)는 컴파일 시 타입을 체크하고, 다양한 데이터 타입을 처리할 수 있도록 도와주는 기능

  • 와일드카드(Generics Wildcard) : 유연한 타입을 허용

      - 기본형 와일드 카드 : List<?>

         => List<?> → "어떤 타입의 리스트인지 모르겠지만 받아들일 수 있음."        

 

      - 상한제한형 와일드 카드 : List<? extends T> T와 T의 하위클래스 허용

         => List<? extends Number> → Number 및 Integer, Double 등 가능

 

      - 하한제한형 와일드 카드 : List<? super T> T와 T의 상위클래스 허용

         => List<? super Integer> → Integer, Number, Object 가능

😂😂😂

와일드 카드 사용예시)

---------------------------------------------------------------------------------------------

 ?  타입이 정확히 필요하지 않고 모든 리스트를 받아야 할 때

? extends T읽기 전용, 리스트에서 값을 가져올 때 사용

? super T쓰기 전용, 리스트에 값을 추가할 때 사용

 

 

2. 제네릭스 활용 예제 및 사용 이유

   1) 제네릭스를 사용하지 않은 클래스 경우 문제점

class Box {
    private Object value;

    public void setValue(Object value) {
        this.value = value;
    }

    public Object getValue() {
        return value;
    }
}

public class Main {
    public static void main(String[] args) {
        Box intBox = new Box();
        intBox.setValue(10);

        Box strBox = new Box();
        strBox.setValue("Hello");

        // 형변환 필요 (타입 안정성 문제 발생 가능)
        int num = (Integer) intBox.getValue(); // ✅ 가능
        String text = (String) strBox.getValue(); // ✅ 가능
    }
}

🚨 문제점: Object 타입이라 형변환이 필요하고, 잘못된 형변환 시 런타임 오류 발생 가능!

 

   2) 제네릭스를 사용한 클래스 경우

class Box<T> {  // "T"는 타입 파라미터 (임의의 타입을 지정 가능)
    private T value;

    public void setValue(T value) {
        this.value = value;
    }

    public T getValue() {
        return value;
    }
}

public class GenericExample {
    public static void main(String[] args) {
        Box<Integer> intBox = new Box<>();  // 정수형 박스
        intBox.setValue(10);
        int num = intBox.getValue();  // 형변환 필요 없음! ✅

        Box<String> strBox = new Box<>();  // 문자열 박스
        strBox.setValue("Hello");
        String text = strBox.getValue();  // 형변환 필요 없음! ✅

        System.out.println(num);   // 10
        System.out.println(text);  // Hello
    }
}

✅ 타입을 유동적으로 변경하여 사용할 수 있다. 

 

📌 제네릭 사용 이유!

✅ 제네릭은 타입 안전성을 높이고, 코드 중복을 줄이는 강력한 기능!

✅ 제네릭 클래스를 사용하면 타입 캐스팅이 필요 없음.

✅ 와일드카드(?)를 사용하면 제네릭을 더 유연하게 활용 가능!

 

3. 제네릭 메서드/인터페이스

     1) 제네릭 메서드

class Util {
    // 제네릭 메서드 정의
    public static <T> void printArray(T[] array) {
        for (T element : array) {
            System.out.print(element + " ");
        }
        System.out.println();
    }
}

public class GenericMethodExample {
    public static void main(String[] args) {
        Integer[] intArray = {1, 2, 3};
        String[] strArray = {"Hello", "World"};

        Util.printArray(intArray);  // ✅ 가능 (출력: 1 2 3)
        Util.printArray(strArray);  // ✅ 가능 (출력: Hello World)
    }
}

메서드에 T를 추가하여 다양한 타입을 받을 수 있도록 설정!
코드를 재사용할 수 있어 효율적!

 

      2) 제네릭 인터페이스

// 제네릭 인터페이스 정의
interface Calculator<T> {
    T add(T a, T b);
}

// Integer 타입 계산기
class IntegerCalculator implements Calculator<Integer> {
    public Integer add(Integer a, Integer b) {
        return a + b;
    }
}

// Double 타입 계산기
class DoubleCalculator implements Calculator<Double> {
    public Double add(Double a, Double b) {
        return a + b;
    }
}

public class GenericInterfaceExample {
    public static void main(String[] args) {
        Calculator<Integer> intCalc = new IntegerCalculator();
        System.out.println(intCalc.add(5, 10));  // 15

        Calculator<Double> doubleCalc = new DoubleCalculator();
        System.out.println(doubleCalc.add(5.5, 2.2));  // 7.7
    }
}

Calculator<T>를 사용해 다양한 타입의 계산기를 만들 수 있음!

 

🎯 제네릭 정리

제네릭 클래스 다양한 타입을 지원하는 클래스 class Box<T> { T value; }
제네릭 메서드 여러 타입을 지원하는 메서드 <T> void printArray(T[] array)
제네릭 인터페이스 타입에 따라 구현이 달라지는 인터페이스 interface Calculator<T> { T add(T a, T b); }
와일드카드 (?) 불특정한 타입을 표현 List<?> list
? extends T T의 하위 타입만 허용 (읽기 전용) List<? extends Number>
? super T T의 상위 타입만 허용 (쓰기 가능) List<? super Integer>