제네릭이란?

strongmhk·2025년 3월 11일
0

Java

목록 보기
9/9

개발을 할 때 코드의 재사용성을 높이고, 안정성을 확보하는 것은 매우 중요하다.

특히 Java에서는 다양한 타입의 데이터를 다루다 보면, 타입 안전성을 유지하면서도 유연한 코드가 필요하다.

이를 효과적으로 해결하는 방법 중 하나가 제네릭(Generic) 이다.


제네릭(Generic)이란 무엇일까?

제네릭(Generic)이란 클래스나 메서드를 정의할 때, 특정 타입을 미리 지정하지 않고 사용할 때 결정할 수 있도록 하는 기능이다.

즉, 코드를 작성할 때 타입을 고정하지 않고, 실제 사용할 때 원하는 타입을 지정할 수 있도록 유연성을 제공한다.

예를 들어, List<String>List<Integer>는 각각 문자열과 정수를 저장하는 리스트지만, 내부적으로는 같은 List<T> 클래스를 사용한다.

여기서 T는 제네릭 타입 매개변수(Type Parameter)이며, 다양한 타입을 처리할 수 있도록 도와준다.


제네릭을 사용한 간단한 예제

class Box<T> {  // 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");
        System.out.println(stringBox.getItem());  // Hello

        Box<Integer> intBox = new Box<>();
        intBox.setItem(100);
        System.out.println(intBox.getItem());  // 100
    }
}

위 코드에서 Box<T> 클래스는 T라는 제네릭 타입을 사용하므로, String이나 Integer 등 다양한 타입을 저장할 수 있다.


제네릭이 왜 필요할까?


1. 타입 안정성 확보

제네릭을 사용하면 컴파일 시점에 타입을 체크할 수 있어, 잘못된 타입이 들어가는 것을 방지할 수 있다.

제네릭이 없던 시절에는 Object를 사용해야 했는데, 이 경우 타입 변환 과정에서 런타임 오류가 발생할 가능성이 컸다.


제네릭을 사용하지 않은 경우 (문제 발생 가능)

import java.util.ArrayList;

public class NonGenericExample {
    public static void main(String[] args) {
        ArrayList list = new ArrayList(); // 타입을 지정하지 않음
        list.add("Hello");
        list.add(100); // 다른 타입도 추가 가능

        String str = (String) list.get(1); // ClassCastException 발생 가능
        System.out.println(str);
    }
}

위 코드에서는 ArrayListStringInteger를 함께 넣을 수 있다.

하지만 String으로 캐스팅할 때 ClassCastException이 발생할 위험이 있다.


제네릭을 사용한 경우 (안전한 코드)

import java.util.ArrayList;

public class GenericExample {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>(); // 타입을 String으로 제한
        list.add("Hello");
        // list.add(100);  // 컴파일 오류 발생

        String str = list.get(0);  // 캐스팅 불필요
        System.out.println(str);
    }
}

제네릭을 사용하면 잘못된 타입의 값이 들어가는 것을 컴파일 시점에 방지할 수 있다.


2. 코드 재사용성 증가

제네릭을 사용하면 하나의 클래스나 메서드를 다양한 타입에 대해 재사용할 수 있다.

예를 들어, 특정 타입의 데이터를 저장하는 Box 클래스를 만들고 싶다고 가정하자.

제네릭이 없다면, 각 타입마다 별도의 클래스를 만들어야 한다.

class StringBox {
    private String item;

    public void setItem(String item) { this.item = item; }
    public String getItem() { return item; }
}

class IntegerBox {
    private Integer item;

    public void setItem(Integer item) { this.item = item; }
    public Integer getItem() { return item; }
}

하지만 제네릭을 사용하면 하나의 클래스만으로 다양한 타입을 처리할 수 있다.

class Box<T> {
    private T item;

    public void setItem(T item) { this.item = item; }
    public T getItem() { return item; }
}

이처럼 제네릭을 사용하면 중복 코드를 줄이고, 유지보수성을 높일 수 있다.


제네릭이 어떻게 활용될 수 있을까?


1. 제네릭 메서드

제네릭은 클래스뿐만 아니라 메서드에서도 활용 가능하다.

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

public class Main {
    public static void main(String[] args) {
        Util.printItem("Hello");  // 문자열 출력
        Util.printItem(123);      // 정수 출력
        Util.printItem(3.14);     // 실수 출력
    }
}

위 코드에서 printItem 메서드는 입력받는 타입을 제한하지 않는다.

따라서 문자열, 정수, 실수 등 어떤 타입의 데이터도 출력할 수 있다.


2. 예제: 온라인 쇼핑몰의 상품 관리

좀 더 와닿는 예제를 생각해보자.

예를 들어, 온라인 쇼핑몰에서 다양한 유형의 상품을 관리하는 시스템을 설계한다고 가정하자.

제네릭을 사용하면 상품의 타입을 제한하지 않고 유연하게 관리할 수 있다.

class Product<T> {
    private String name;
    private T value; // 가격, 크기, 무게 등 다양한 속성을 저장 가능

    public Product(String name, T value) {
        this.name = name;
        this.value = value;
    }

    public void displayInfo() {
        System.out.println("상품명: " + name + ", 속성: " + value);
    }
}

public class ShoppingMall {
    public static void main(String[] args) {
        Product<Integer> laptop = new Product<>("노트북", 1500000); // 가격 (정수)
        Product<Double> apple = new Product<>("사과", 1.2); // 무게 (실수)
        Product<String> tshirt = new Product<>("티셔츠", "Large"); // 사이즈 (문자열)

        laptop.displayInfo();
        apple.displayInfo();
        tshirt.displayInfo();
    }
}

실행 결과

상품명: 노트북, 속성: 1500000
상품명: 사과, 속성: 1.2
상품명: 티셔츠, 속성: Large

3. 컬렉션 프레임워크에서의 활용

제네릭은 자바의 컬렉션 프레임워크에서도 필수적으로 사용된다.

import java.util.ArrayList;
import java.util.List;

public class GenericListExample {
    public static void main(String[] args) {
        List<String> names = new ArrayList<>();
        names.add("Alice");
        names.add("Bob");

        for (String name : names) {
            System.out.println(name);
        }
    }
}

위 코드에서 List<String>을 사용하면 String 타입의 데이터만 저장할 수 있다.

덕분에 타입 안정성이 보장되고, 불필요한 형 변환을 피할 수 있다.


결론

제네릭은 타입 안정성을 높이고, 코드의 재사용성을 증가시키며, 유지보수성을 개선하는 강력한 기능이다.

컬렉션, 유틸리티 클래스 등 다양한 곳에서 제네릭이 활용될 수 있다.

제네릭을 잘 활용하면 유연하면서도 안정적인 코드를 작성할 수 있다.

제네릭을 적극적으로 활용해 불필요한 형 변환을 줄이고, 더 안전한 코드를 작성해보자!

profile
저 커서 개발자가 될래요!

0개의 댓글