[Java]Generics

한솔·2025년 7월 18일

Generics

제네릭이란?

List에서 <> 안의 String.
자료형(타입)을 코드가 실행되기 전에 미리 고정하지 않고 외부에서 나중에 넣게끔 설계하는 문법이다.
쉽게 말하면 자료형을 직접 넣지 말고 <>로 비워두고 나중에 이건 String이야~, 이건 Integer야~ 라고 지정하게 하자.

왜 제네릭을 쓰는가?

타입마다 클래스를 일일이 만들면 중복이 심하고 유지보수도 어려움.
제네릭은 하나의 클래스만 정의해두고 모든 타입에 재사용 가능하게 해준다.

→ 아래 제네릭 전/후 예시 참고.

public class StringBox {
    private String item;

    public void set(String item) {
        this.item = item;
    }

    public String get() {
        return item;
    }
}

위 코드와 같은 클래스가 있다고 가정해보자. 이거는 String 만 담을 수 있는 박스. 그럼 int도 담고 싶을 때 또 만들어야 하나?라는 생각을 할 수 있다.

public class IntegerBox {
    private Integer item;
    public void set(Integer item) { this.item = item; }
    public Integer get() { return item; }
}

public class DoubleBox {
    private Double item;
    public void set(Double item) { this.item = item; }
    public Double get() { return item; }
}

public class BooleanBox {
    private Boolean item;
    public void set(Boolean item) { this.item = item; }
    public Boolean get() { return item; }
}

이건 타입마다 클래스를 일일이 새로 만들어야 한다. 중복이 너무 심함.

그래서 이런 중복을 없애고 하나의 클래스로 모든 타입을 처리할 때 제네릭.

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

따라서 제네릭은 재사용 가능한 코드를 만들기 위해 필수

또한 List는 내부 요소의 타입을 모르는 상태로 만들어진다. 그래서 타입 매개변수를 필요로 함.

그래서 List 이런 식으로 선언되어 있고 사용 시 List처럼 구체적인 타입을 만들어줘야 한다.

즉, List, Map, Set같은 컬렉션은 내부적으로 제네릭으로설계되어 있다.

생성된 제네릭은 아래 코드와 같이 사용하면 된다.

Box<String> strBox = new Box<>();       // StringBox 역할
Box<Integer> intBox = new Box<>();      // IntegerBox 역할
Box<Double> doubleBox = new Box<>();    // DoubleBox 역할
Box<Boolean> boolBox = new Box<>();     // BooleanBox 역할
  

제네릭의 기초 문법

  class Box<T> {
    private T item;

    public void set(T item) { this.item = item; }
    public T get() { return item; }
}

T는 타입 매개변수(Type parameter)

클래스나 메서드가 어떤 타입을 받을지 미정인 상태로 설계 가능하다.

  Box<String> strBox = new Box<>();
strBox.set("Hello");
String val = strBox.get(); // 형변환 없이 사용 가능!

제네릭 클래스 vs 제네릭 메서드

제네릭 클래스

  class Container<T> {
    T data;
}

제네릭 메서드

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

가 리턴 타입 앞에 있다. 메서드 자체가 제네릭이라는 뜻이다.

제네릭 사용 시 주의 사항

    class MyClass<T> { ... }   // 제네릭 클래스
<T> void myMethod(T val)   // 제네릭 메서드
 List<int> list = new ArrayList<>();  // 오류 (원시 타입 불가)
Box<String> b = new Box<>();
b.set("hi"); // ok
b.set(123);  // 오류 (String만 허용)

질문사항

Q1. <> 안엔 꼭 T여야 해?

No. 관례적으로 T를 많이 씀

Q2. <?>는 뭐야?

와일드카드. “모르겠지만 뭔가 타입이 있을 거야” 라는 뜻.
제한을 줄 수 있음 → <? extends Number>, <? super Integer>

Q3. 제네릭은 형변환 안 해도 된다고?

Yes
제네릭은 컴파일 타임에 타입을 검사해줌 → 실수 예방, 가독성 향상

List names = new ArrayList(); // 제네릭 미사용
names.add("hi");
String name = (String) names.get(0); // 형변환 필요 x 위험

List<String> names = new ArrayList<>(); // 제네릭 사용
String name = names.get(0); // 형변환 필요 없음 안전

Q4. 제네릭 클래스 vs 제네릭 메서드 구분 맞아?

Yes,

  • 제네릭 클래스
public class Box<T> {
    T item;
}
// → T는 클래스 전체에 적용
  • 제네릭 메서드
public class Util {
    public static <T> void print(T value) {
        System.out.println(value);
    }
}
// → <T>가 메서드 안에서만 사용됨. 클래스 전체는 제네릭 아님.

0개의 댓글