# 제네릭이란?
Java에서 제네릭은 코드를 재사용하기 쉽게 만들고 동시에 타입 안정성(type safety) 을 보장하기 위한 문법이다.
클래스나 메서드를 선언할 때 타입을 미리 정하지 않고 실제 사용할 시점에서 타입을 결정하는 형태로 동작한다.
제네릭은 <>(다이아몬드) 기호로 표현된다.
제네릭을 사용할 경우 컴파일 시점에서 타입을 보장할 수 있어 형변환 오류처럼 런타임에서 발생하는 문제를 방지할 수 있다.
# 제네릭을 사용하는 이유
타입 안정성(Type Safety)
제네릭을 사용하지 않으면 모든 값이 Object 형태로 저장된다.
이 경우 꺼낼 때 직접 캐스팅해야 하고 잘못된 타입으로 캐스팅하면 런타임 오류가 발생한다.
List list = new ArrayList();
list.add("hello");
list.add(3); // 서로 다른 타입 추가 가능 → 문제 발생 가능
String str = (String) list.get(0); // 캐스팅 필요
제네릭을 사용하면 컴파일러가 타입을 미리 검사하기 때문에 잘못된 타입의 객체가 들어가는 것을 방지한다.
List<String> list = new ArrayList<>();
list.add("hello"); // 타입 안정성 확보
String str = list.get(0); // 캐스팅 불필요
# 코드 재사용성(Code Reusability)
제네릭을 사용하면 하나의 코드로 여러 타입을 처리할 수 있다.
예를 들어 동일한 로직을 정수 리스트에도 사용할 수 있고 문자열 리스트에도 사용할 수 있다.
public static <T> void printList(List<T> lists) {
for (T t : lists) {
System.out.println(t);
}
}
제네릭 기본 예제
package generic_;
import java.util.ArrayList;
import java.util.List;
public class GenericT {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("hello");
String str = list.get(0);
}
}
제네릭을 사용하면 불필요한 캐스팅이 필요 없고 타입 오류도 컴파일 시점에 잡힌다.
# 제네릭 메서드를 이용한 코드 재사용
public static <T> void printList(List<T> lists) {
for (T t : lists) {
System.out.println(t);
}
}
var intList = Arrays.asList(1, 2, 3);
var strList = Arrays.asList("A", "B", "C");
printList(intList);
printList(strList);
같은 코드로 여러 타입을 처리할 수 있어 재사용성이 크게 향상된다.
# 제네릭 실전 활용
제네릭은 특히 상속 구조가 있는 클래스들을 다룰 때 유용하다.
아래 예제는 Fruit의 하위 클래스인 Apple, Banana 등을 하나의 박스에서 다루기 위해 FruitBox<T> 제네릭 클래스를 만들었다.
Fruit → Apple / Banana 구조
class Fruit {
public void consume() {
System.out.println("Consuming a fruit!");
}
}
class Apple extends Fruit {
@Override
public void consume() {
System.out.println("Eating an Apple");
}
}
class Banana extends Fruit {
@Override
public void consume() {
System.out.println("Eating a Banana");
}
}
제네릭 클래스: FruitBox<T extends Fruit>
class FruitBox<T extends Fruit> {
private List<T> lists = new ArrayList<>();
public void add(T fruit) {
lists.add(fruit);
}
public T get(int index) {
return lists.get(index);
}
public void consumeAll() {
for (T t : lists) t.consume();
}
}
💡 T extends Fruit
👉 Fruit 또는 그 자식만 추가할 수 있도록 타입을 제한
👉제네릭의 bounded type 활용 예
실행 예제
class Main {
public static void main(String[] args) {
FruitBox<Fruit> fruitBox = new FruitBox<>();
fruitBox.add(new Apple());
fruitBox.add(new Banana());
fruitBox.consumeAll();
}
}
출력:
Eating an Apple Eating a Banana
| 구분 | 내용 |
| 목적 | 타입 안정성 확보 + 코드 재사용성 증가 |
| 문법 | <> 다이아몬드 기호 |
| 동작 방식 | 컴파일 시점에 타입 검사 |
| 장점 | 캐스팅 불필요, 오류 예방, 유지보수 용이 |
| 실무 활용 | API 응답 구조, 컬렉션, 유틸리티 클래스, 공통 모듈 |
'java' 카테고리의 다른 글
| Stream (0) | 2025.11.28 |
|---|---|
| Lambda Expression(=람다식) (0) | 2025.11.28 |
| Abstract class(=추상화) (0) | 2025.11.28 |
| Encapsulation(=캡슐화) (0) | 2025.11.28 |
| Java 문법 – Overload vs Override (0) | 2025.11.28 |
