본문 바로가기
프로그래밍/JAVA

Java 제네릭(Generic) 쉽게 이해하기 – 타입 안정성과 코드 재사용성 극대화

by 곰 옥수수 2025. 3. 4.
728x90
반응형

Java에서 제네릭(Generic)이란?

제네릭(Generic)의 정의

제네릭(Generic)이란, 데이터 타입을 일반화하여 컴파일 시 타입을 체크하고, 타입 안정성을 높이는 Java의 기능이다.
제네릭을 사용하면 타입 안정성(Type Safety)을 보장하면서도 코드의 재사용성을 높일 수 있다.

제네릭이 왜 필요할까? 

Java의 제네릭(Generic)은 컴파일 시 타입을 체크(타입 세이프)하여 코드의 안정성과 재사용성을 높이는 기능이다.


1. 제네릭의 기본 개념

  • 타입 안정성 보장: 컴파일 시점에서 타입 체크를 하므로, 런타임에 발생할 수 있는 ClassCastException을 방지할 수 있음
  • 코드 재사용성 증가: 특정 타입에 의존하지 않으므로 여러 타입에서 동일한 코드 사용 가능
  • 가독성 및 유지보수성 향상: 불필요한 형변환을 제거하여 코드가 간결해짐

제네릭을 사용하지 않는 경우 (비제네릭 코드)

import java.util.*;

public class NonGenericList {
    public static void main(String[] args) {
        List list = new ArrayList();
        list.add("Hello");
        list.add(10); // 문제 발생 가능 (String을 예상했지만 Integer가 들어감)

        for (Object obj : list) {
            String str = (String) obj; // 형변환 필요, ClassCastException 가능성 있음
            System.out.println(str);
        }
    }
}

문제점:

  • Object 타입을 사용하므로 모든 타입을 저장할 수 있지만, 타입 안정성이 보장되지 않음
  • String을 기대했지만 Integer 값(10)이 들어갈 수 있어 ClassCastException이 발생할 위험이 있음
  • 매번 형변환이 필요 ((String) obj)

2. 제네릭을 사용한 경우

import java.util.*;

public class GenericList {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>(); // String 타입만 저장 가능
        list.add("Hello");
        // list.add(10);  // ❌ 컴파일 에러 발생 (타입 불일치)

        for (String str : list) {
            System.out.println(str); // 형변환 불필요
        }
    }
}

장점:

  • 컴파일 시 타입 체크 → list.add(10);에서 바로 오류 발생
  • 형변환 불필요 → String str = (String) obj; 같은 코드 제거
  • 타입 안정성 보장 → 코드가 더욱 안전해짐

3. 제네릭 클래스 만들기

기본적인 제네릭 클래스

// <T>를 사용하여 타입을 매개변수화함
class Box<T> {
    private T value;

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

    public T get() {
        return value;
    }
}

public class GenericClassExample {
    public static void main(String[] args) {
        Box<String> stringBox = new Box<>(); // String 타입 사용
        stringBox.set("Hello");
        System.out.println(stringBox.get()); // Hello

        Box<Integer> intBox = new Box<>(); // Integer 타입 사용
        intBox.set(100);
        System.out.println(intBox.get()); // 100
    }
}

T(Type Parameter)

  • T는 타입 매개변수(Type Parameter)로, 나중에 실제 타입을 지정할 수 있음
  • T 외에도 E(Element), K(Key), V(Value) 등을 사용할 수 있음

4. 제네릭 메서드

제네릭 메서드 정의 방법

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) {
        String[] strArray = {"Apple", "Banana", "Cherry"};
        Integer[] intArray = {1, 2, 3};

        Util.printArray(strArray); // Apple Banana Cherry
        Util.printArray(intArray); // 1 2 3
    }
}

장점:

  • 다양한 타입을 지원하는 범용 메서드
  • 형변환 없이 타입을 유지한 채 사용 가능

5. 와일드카드 (?)

와일드카드 개념

  • ?는 알 수 없는 타입(Unknown Type) 을 나타냄
  • 제네릭 타입이 어떤 타입이든 받을 수 있도록 유연성을 제공

<?> 와일드카드 예제

import java.util.*;

class WildcardExample {
    // 와일드카드를 사용한 메서드
    public static void printList(List<?> list) {
        for (Object obj : list) {
            System.out.print(obj + " ");
        }
        System.out.println();
    }

    public static void main(String[] args) {
        List<String> strList = Arrays.asList("Apple", "Banana", "Cherry");
        List<Integer> intList = Arrays.asList(1, 2, 3);

        printList(strList); // Apple Banana Cherry
        printList(intList); // 1 2 3
    }
}

<?> 활용 목적

  • 어떤 타입이든 받을 수 있음 (List<String>, List<Integer> 등)
  • 읽기 전용으로 활용 가능 (데이터를 수정하지 않음)

6. 제한된 제네릭 (Bounded Type)

extends 키워드를 사용하여 특정 타입만 허용

class NumberBox<T extends Number> { // Number 또는 하위 클래스만 허용
    private T value;

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

    public T get() {
        return value;
    }
}

public class BoundedTypeExample {
    public static void main(String[] args) {
        NumberBox<Integer> intBox = new NumberBox<>();
        intBox.set(100);
        System.out.println(intBox.get()); // 100

        // NumberBox<String> strBox = new NumberBox<>(); // ❌ 컴파일 오류 (String은 Number의 하위 타입이 아님)
    }
}

T extends Number의 의미

  •  Number 또는 그 하위 타입(Integer, Double, Float 등)만 사용 가능
  •  숫자 연산이 필요한 경우 유용

7. 타입 파라미터(Type Parameter)란?

정의

타입 파라미터(Type Parameter)란 제네릭에서 사용되는 타입 변수(Placeholder)로, 실제 타입이 아닌 임시적인 타입을 의미한다.

자바에서는 대문자 1글자로 작성하는 것이 일반적이며, 다음과 같은 이름이 자주 사용된다.

 

타입  파라미터의미
T Type (가장 일반적인 이름)
E Element (컬렉션 프레임워크에서 자주 사용됨)
K Key (맵의 키)
V Value (맵의 값)
N Number (숫자 타입을 의미)

타입 파라미터 예제

public class GenericMethodExample {
    
    // 두 개의 값을 비교하는 제네릭 메서드
    public static <T> boolean isEqual(T a, T b) {
        return a.equals(b);
    }

    public static void main(String[] args) {
        System.out.println(isEqual(10, 10));           // true
        System.out.println(isEqual("Java", "Java"));   // true
        System.out.println(isEqual(3.14, 2.71));       // false
    }
}

실행 결과

true
true
false

isEqual 메서드는 타입 T를 받아, 동일한 타입의 두 값을 비교하는 기능을 제공한다.


정리

개념 설명 예제
제네릭 클래스 클래스에서 타입을 매개변수화 class Box<T> {}
제네릭 메서드 메서드에서 타입을 매개변수화 <T> void print(T item) {}
와일드카드 (?) 불특정한 타입을 받을 때 사용 List<?> list
제한된 타입 (extends) 특정 상위 클래스만 허용 <T extends Number>
타입 파라미터 T, E, K, V 등으로 표현되는 제네릭에서 사용하는 타입 변수

제네릭을 사용하면 타입 안정성을 보장하면서 코드의 유연성과 재사용성을 극대화할 수 있다.

 

Java 제네릭 와일드카드 사용법

 

Java 제네릭 와일드카드 사용법 | ?, ? extends, ? super 차이점 한눈에!

제네릭 와일드카드(Generics Wildcards)제네릭 와일드카드(Generics Wildcards)는 제네릭 타입을 좀 더 유연하게 사용하기 위해 제공되는 기능이다. 주로 메서드에서 제네릭 타입을 사용할 때 타입의 불확

bears-paw.tistory.com

 

728x90
반응형

댓글