본문 바로가기
이직&취업/Java 기초 상식

Java Generic(제네릭) 란?

by journeylabs 2025. 3. 18.
728x90
반응형

목차

    Java Generic(제네릭) 란?

    1. Generic(제네릭)이란?

    Java에서 **제네릭(Generic)**은 클래스나 메서드에서 사용할 데이터 타입을 미리 지정하지 않고, 나중에 필요할 때 지정할 수 있도록 하는 기능이다. 즉, 컴파일 시점에서 타입을 지정하여 코드의 재사용성을 높이고, 타입 안정성을 보장할 수 있다.

    제네릭을 사용하면 컴파일 시 타입 체크가 가능하여 실행 시 발생할 수 있는 ClassCastException과 같은 오류를 방지할 수 있다.


    2. Generic의 장점

    1. 타입 안정성(Type Safety): 컴파일 시점에서 타입을 체크하여 잘못된 타입 사용을 방지할 수 있다.
    2. 코드 재사용성 증가: 하나의 클래스 또는 메서드를 다양한 타입으로 활용할 수 있다.
    3. 형 변환(Casting) 필요 없음: 불필요한 형 변환을 줄여 코드의 가독성을 향상시킨다.
    4. 유지보수 용이: 코드의 일관성이 유지되어 디버깅과 유지보수가 용이하다.

    3. Generic 사용 방법

    1) 클래스에서 제네릭 사용

    제네릭을 사용하여 클래스에서 다양한 타입을 처리할 수 있도록 만들 수 있다.

    class Box<T> {
        private T item;
        public void setItem(T item) { this.item = item; }
        public T getItem() { return item; }
    }

     

    사용 예제

    public class Main {
        public static void main(String[] args) {
            Box<String> stringBox = new Box<>();
            stringBox.setItem("Hello Generic");
            System.out.println(stringBox.getItem()); // 출력: Hello Generic
    
            Box<Integer> intBox = new Box<>();
            intBox.setItem(100);
            System.out.println(intBox.getItem()); // 출력: 100
        }
    }

    위 예제에서 Box<T> 클래스는 T 타입의 데이터를 저장할 수 있으며, String 또는 Integer 같은 특정 타입으로 사용할 수 있다.

    2) 메서드에서 제네릭 사용

    메서드에서도 제네릭을 사용할 수 있으며, 다양한 타입의 값을 처리할 수 있다.

    public class Util {
        public static <T> void printItem(T item) {
            System.out.println("Item: " + item);
        }
    }

     

    사용 예제

    public class Main {
        public static void main(String[] args) {
            Util.printItem("Generic Method"); // 출력: Item: Generic Method
            Util.printItem(123); // 출력: Item: 123
        }
    }

    제네릭 메서드는 <T>를 사용하여 다양한 타입을 인자로 받을 수 있으며, printItem 메서드는 어떤 타입이든 매개변수로 받아 출력할 수 있다.


    4. Generic 타입

    1) 여러 개의 타입 매개변수 사용

    제네릭 클래스에서 여러 개의 타입 매개변수를 사용할 수도 있다.

    class Pair<K, V> {
        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; }
    }

     

    사용 예제

    public class Main {
        public static void main(String[] args) {
            Pair<String, Integer> pair = new Pair<>("Age", 25);
            System.out.println(pair.getKey() + ": " + pair.getValue()); // 출력: Age: 25
        }
    }

    이처럼 Pair<K, V> 클래스를 활용하면 서로 다른 두 개의 타입을 하나의 객체로 다룰 수 있다.

    2) 제네릭 인터페이스 사용

    인터페이스에서도 제네릭을 적용할 수 있으며, 이를 구현하는 클래스는 특정 타입을 사용할 수 있다.

    interface Container<T> {
        void add(T item);
        T get();
    }
    
    class StringContainer implements Container<String> {
        private String item;
        public void add(String item) { this.item = item; }
        public String get() { return item; }
    }

     

    사용 예제

    public class Main {
        public static void main(String[] args) {
            StringContainer container = new StringContainer();
            container.add("Hello Interface");
            System.out.println(container.get()); // 출력: Hello Interface
        }
    }

    Container<T> 인터페이스를 구현한 StringContainer 클래스는 String 타입만 처리하도록 제한된다.


    5. Generic 선언 및 생성 방법

    1) 클래스에서 선언

    class Sample<T> {
        private T value;
        public Sample(T value) { this.value = value; }
        public T getValue() { return value; }
    }

    이 클래스는 T 타입의 데이터를 저장하고 반환할 수 있으며, 특정 타입을 지정하여 사용할 수 있다.

    2) 메서드에서 선언

    public class Utility {
        public static <T> T getFirstElement(T[] array) {
            return array.length > 0 ? array[0] : null;
        }
    }

    이 메서드는 배열의 첫 번째 요소를 반환하는데, 입력되는 배열의 타입에 따라 유연하게 작동한다.

    3) 인터페이스에서 선언

    interface DataProcessor<T> {
        void process(T data);
    }

    이 인터페이스는 T 타입의 데이터를 처리하는 기능을 제공하며, 다양한 타입에 맞게 구현될 수 있다.

     

    6. 제한된 Generic(제네릭)과 와일드 카드

    1) 상한 제한 (extends)

    제네릭 타입 매개변수에 특정 타입의 하위 클래스만 허용하고 싶을 때 extends 키워드를 사용한다.

    class NumberBox<T extends Number> {
        private T number;
        public NumberBox(T number) { this.number = number; }
        public T getNumber() { return number; }
    }

     

    사용 예제

    public class Main {
        public static void main(String[] args) {
            NumberBox<Integer> intBox = new NumberBox<>(10);
            NumberBox<Double> doubleBox = new NumberBox<>(3.14);
            
            System.out.println("Integer Box: " + intBox.getNumber());
            System.out.println("Double Box: " + doubleBox.getNumber());
        }
    }

    NumberBox<T extends Number>Number의 하위 클래스(Integer, Double 등)만 사용 가능하다. NumberBox<String>과 같이 Number의 하위 클래스가 아닌 타입을 지정하면 컴파일 오류가 발생한다.

    2) 하한 제한 (super)

    제네릭에서 특정 타입의 상위 클래스만 허용하려면 super 키워드를 사용한다.

    class DataProcessor<T> {
        public void processData(T data) {
            System.out.println("Processing: " + data);
        }
    }
    
    public class Main {
        public static void processNumberBox(DataProcessor<? super Integer> processor, Integer data) {
            processor.processData(data);
        }
        
        public static void main(String[] args) {
            DataProcessor<Number> numberProcessor = new DataProcessor<>();
            processNumberBox(numberProcessor, 100); // 가능
        }
    }

    위 코드에서 DataProcessor<? super Integer>Integer상위 타입(Number, Object 등)만 사용할 수 있다.

    3) 와일드 카드 (?)

    와일드 카드는 제네릭 타입을 유연하게 사용할 수 있도록 한다. 다음과 같이 세 가지 형태로 사용된다.

    (1) 제한 없음 (? 만 사용)

    public static void printList(List<?> list) {
        for (Object item : list) {
            System.out.println(item);
        }
    }

    List<?>어떠한 타입의 리스트든 허용한다.

    List<String> strList = Arrays.asList("A", "B", "C");
    List<Integer> intList = Arrays.asList(1, 2, 3);
    printList(strList);
    printList(intList);

    (2) 상한 제한 (extends)

    public static void printNumbers(List<? extends Number> list) {
        for (Number num : list) {
            System.out.println(num);
        }
    }

    List<? extends Number>Number와 그 하위 클래스(Integer, Double 등)만 허용한다.

    List<Integer> intList = Arrays.asList(1, 2, 3);
    List<Double> doubleList = Arrays.asList(1.1, 2.2, 3.3);
    printNumbers(intList);
    printNumbers(doubleList);

    (3) 하한 제한 (super)

    public static void addNumbers(List<? super Integer> list) {
        list.add(100);
    }

    List<? super Integer>Integer의 상위 타입(Number, Object)만 허용한다.

    List<Number> numList = new ArrayList<>();
    addNumbers(numList);

    와일드 카드를 사용하면 유연한 타입 매칭이 가능하지만, 읽기/쓰기 제약이 존재하므로 상황에 맞게 사용해야 한다.


    위 내용을 통해 제한된 제네릭과 와일드 카드에 대한 개념과 사용법을 자세히 이해할 수 있다. 추가적으로 궁금한 점이 있으면 언제든지 질문해주세요! 😊

    728x90
    반응형