IT 개발 라이프/Back_End

자바에서 Generic을 쓰는 이유

10Biliion 2024. 12. 27. 15:20

1. 코드 재사용성

Generics를 사용하면 다양한 타입에서 동작하는 클래스를 하나의 일반화된 코드로 작성할 수 있습니다.

  • 동일한 로직을 구현하기 위해 여러 클래스나 메서드를 작성할 필요가 없습니다.

Generic을 사용하지 않은 경우

class StringBox {
    private String value;

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

    public String getValue() {
        return value;
    }
}

class IntegerBox {
    private Integer value;

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

    public Integer getValue() {
        return value;
    }
}

위처럼 타입별로 클래스를 작성하는 대신, Generic을 사용하면 하나의 클래스로 해결할 수 있습니다:

Generic을 사용한 경우

class Box<T> {
    private T value;

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

    public T getValue() {
        return value;
    }
}

이제 Box를 여러 타입에 대해 재사용할 수 있습니다:

Box<String> stringBox = new Box<>();
stringBox.setValue("Hello");

Box<Integer> intBox = new Box<>();
intBox.setValue(123);

 

2. 타입 안정성 (Type Safety)

Generics를 사용하면 컴파일 시 타입 체크를 강제하여, 런타임에 발생할 수 있는 ClassCastException을 방지합니다.

Generic을 사용하지 않은 경우

List list = new ArrayList();
list.add("Hello");
list.add(123); // 의도하지 않은 값 추가

String str = (String) list.get(1); // 런타임에 ClassCastException 발생

Generic을 사용한 경우

List<String> list = new ArrayList<>();
list.add("Hello");
// list.add(123); // 컴파일 에러 발생

String str = list.get(0); // 안전한 타입 반환

Generic을 사용하면 다른 타입의 데이터가 잘못 추가되는 것을 방지하여, 런타임 오류 대신 컴파일 타임에 오류를 잡을 수 있습니다.

 

3. 컴파일 타임 타입 체크

Generic을 사용하면 컴파일러가 타입을 체크하므로, 개발 중 타입 관련 오류를 발견하기 쉽습니다.
이는 런타임 오류를 줄이고, 디버깅 시간을 절약합니다.

// Generic 사용 시
List<Integer> numbers = new ArrayList<>();
numbers.add(10);
numbers.add("Hello"); // 컴파일 에러: String은 Integer 타입이 아님

Generic이 없으면 런타임에서야 오류를 알게 됩니다. 하지만 Generic을 사용하면 컴파일러가 잘못된 타입을 미리 감지합니다.

 

4. 가독성 및 유지보수성 향상

Generic을 사용하면 타입 변환(Casting)을 명시적으로 하지 않아도 되므로 코드가 간결해지고, 가독성과 유지보수성이 향상됩니다.

Generic 없이 타입 변환

List list = new ArrayList();
list.add("Hello");
String str = (String) list.get(0); // 명시적 타입 변환 필요

Generic을 사용한 경우

List<String> list = new ArrayList<>();
list.add("Hello");
String str = list.get(0); // 타입 변환 불필요

 

5. 제네릭 메서드를 통한 유연한 설계

Generic은 메서드 수준에서도 사용할 수 있어 더욱 유연한 설계를 가능하게 합니다.

제네릭 메서드

public static <T> void printArray(T[] array) {
    for (T element : array) {
        System.out.println(element);
    }
}

public static void main(String[] args) {
    Integer[] intArray = {1, 2, 3};
    String[] strArray = {"A", "B", "C"};

    printArray(intArray);
    printArray(strArray);
}

위의 printArray 메서드는 다양한 타입의 배열에 대해 동작할 수 있습니다.

 

6. 와일드카드로 유연성 제공

Generic은 와일드카드(?)를 통해 특정 범위의 타입을 처리하거나 제한할 수 있습니다.

와일드카드 사용

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

범위 제한

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

 

 

결론

  1. 코드 재사용성: 다양한 타입을 하나의 코드로 처리 가능.
  2. 타입 안정성: 컴파일 시 타입 체크를 통해 런타임 오류 방지.
  3. 코드 가독성: 타입 변환을 줄여 코드 간결화.
  4. 유지보수성 향상: 타입 안정성을 통해 오류 수정 시간 단축.