29. 제네릭(Generic)

박은서's avatar
Jan 04, 2026
29. 제네릭(Generic)

1. 제네릭(Generic)

교재 p.551-557 13.1 제네릭 프로그래밍

1️⃣ 제네릭이란?

1) 개념

클래스·메서드에서 사용할 타입을 미리 고정하지 않고, 사용하는 시점에 결정하는 문법
List<String> list = new ArrayList<>();
➡️ String이라는 타입 파라미터를 넘겨서
➡️ 컴파일 시 타입을 체크하게 만드는 기능
📌 한 줄 요약 “타입을 변수처럼 사용하는 문법”

2) 목적

  • 타입 안정성
  • 형변환 제거
  • 재사용성 향상

3) 예시

package ex13; // new 할 때 타입을 결정할 수 있게 해주는 제네릭 class Box2<T> { // 꺽쇠 쓰고 영어 대문자(약속) -> new할 때 타입 결정함(타입 결정 안 하면 무조건 Object 타입으로 new됨) T data; } public class Ge02 { public static void main(String[] args) { Box2<String> box2 = new Box2(); // new할 때 Box2 클래스의 T타입이 String타입이 됨 // box2.data = 1; -> new할 때 String으로 타입 결정됐기 때문에 1 넣으면 에러남 box2.data = "안녕"; int len = box2.data.length(); System.out.println(len); Box2<Integer> box3 = new Box2(); // int를 못쓰기 때문에 래퍼클래스 입력해야 함 box3.data = 5; System.out.println(box3.data); } }

2️⃣ 제네릭의 필요성

❌ 제네릭이 없던 시절 (문제점)

List list = new ArrayList(); list.add("hello"); list.add(123); String s = (String) list.get(1);// ❌ 런타임 에러
  • 컴파일 OK
  • 실행 중 ClassCastException

⭕ 제네릭 사용 후

List<String> list = new ArrayList<>(); list.add("hello"); // list.add(123); ❌ 컴파일 에러 String s= list.get(0);// 형변환 불필요
✔️ 컴파일 시점에 오류 발견
✔️ 타입 안정성 확보

3️⃣ 제네릭 기본 문법

class Box<T> { private T value; public void set(T value) { this.value = value; } public T get() { return value; } }
Box<Integer> box = new Box<>(); box.set(10); Integern= box.get();
 

2. 제네릭과 Object

1️⃣ 제네릭 이전 : Object 기반 설계

1) Object는 모든 클래스의 부모

class Box { private Object value; public void set(Object value) { this.value = value; } public Object get() { return value; } }
Box box = new Box(); box.set("hello"); String s= (String) box.get(); // 형변환 필요
➡️ Object를 자료형으로 사용할 때 넣기는 편하지만 꺼내서 쓸 때는 불편함
오브젝트의 자식 클래스에서 메서드를 끌어다 사용하려면 다운캐스팅 해야 함
package ex13; // Object의 장단점 class Box { Object data; } public class Ge01 { public static void main(String[] args) { Box box = new Box(); box.data = "안녕"; String castData = (String) box.data; int len = castData.length(); System.out.println(len); } }

2️⃣ Object 방식의 장점

1) 모든 타입 저장 가능

box.set("hello"); box.set(123); box.set(newUser());
  • 타입에 제약 없음
  • 구조 단순

2) 제네릭 문법 없이도 재사용 가능

  • Java 1.4 이전 표준 방식
  • 하나의 클래스 재사용 가능

3️⃣ Object 방식의 단점

1) ❌ 타입 안정성(Type Safety) 붕괴

box.set(123); String s= (String) box.get(); // 런타임 에러
  • 컴파일 시점에 오류를 못 잡음
  • 실행 중 ClassCastException

2) ❌ 형변환(Casting) 남발

User u= (User) box.get();
  • 코드 가독성 저하
  • 실수 가능성 증가

3) ❌ IDE & 컴파일러의 도움 상실

  • 자동완성 부정확
  • 타입 추론 불가
  • 리팩토링 위험

4) ❌ 의도 파악이 어려움

Box box = new Box();
➡️ 이 Box에 무슨 타입이 들어가는지 알 수 없음

4️⃣ 해결책 : 제네릭 등장

⭕ 제네릭으로 개선

class Box<T> { private T value; public void set(T value) { this.value = value; } public T get() { return value; } }
Box<String> box = new Box<>(); box.set("hello"); String s= box.get(); // 캐스팅 불필요

5️⃣ Object vs Generic 비교 표

항목
Object 기반
Generic 기반
타입 안정성
❌ 런타임
⭕ 컴파일 타임
형변환
❌ 필수
⭕ 제거
오류 발견 시점
실행 중
컴파일 시
가독성
낮음
높음
IDE 지원
약함
강력
재사용성
유지보수
어려움
쉬움
 

3. Object 사용이 불가피한 상황

1️⃣ Object 사용이 불가피한 상황

1) new의 제어권이 없다

객체 생성 시점 ≠ 객체 사용 시점
  • 프레임워크
  • 라이브러리
  • 플랫폼 코드
  • 1차 개발자(기반 설계자)
➡️ 사용자가 new 할 수 없음

2) 생성 시점에 타입을 모른다

“언젠가 무언가가 들어올 건 아는데, 지금은 모른다”
  • 플러그인
  • 사용자 입력
  • 설정 파일
  • 외부 확장
➡️ 제네릭도 쓸 수 없음
⚠️ 제네릭은 컴파일 시 타입 확정이 필요

3) 나중에 타입을 주입해야 한다

  • setter
  • 캐스팅
  • 초기화 메서드
  • 컨테이너 방식
➡️ 이 경우, Object가 임시 컨테이너 역할

2️⃣ 예시 (동물원)

1) 예시 내용

나는 동물원 주인. 동물원을 만들거야. 설계는 건축업자가 해줘야 되. 내 돈으로 안하고 나라의 투자로 만들거야. 나라에 투자를 받으러 가니까 나라가 빈 땅이라 못 준대. 구색이라도 맞추래. 그걸 보고 투자를 해준대. 그래서 일단 구색은 맞추려고 방을 만들려고 해. 건축업자(클래스 설계하는 사람)한테 방 만들어달라니까 동물 뭐 넣을꺼냐고 물어봐. 근데 아직 안정해졌어. 뉴해서 방 만들어야 하는데 동물을 아직 모르니까 일단 오브젝트로 만드는거야. 사자도 들어갈 수 있고, 토끼도 들어갈 수 있게. 그럼 이제 투자를 받았어. 기린이 들어올거야. 기린이 들어갈 방이 필요해. 그럼 내가 뉴할때 기린에게 필요한 방을 만들 수 있어.

2) 정리

비유
개발 개념
동물원 주인
서비스 사용자
건축업자
프레임워크/1차 개발자
객체
동물
실제 타입
투자 받기 전
타입 미정 상태
핵심 포인트
  • 방(객체)을 미리 만들어야 함
  • 동물(타입)은 나중에 결정
  • 방은 범용적이어야 함
class Cage { Object animal; // 아직 모름 }
cage.animal = new Giraffe();// 나중에 확정
✔️ Object를 써야만 했던 이유 성립

3️⃣ 예시 (상가 건물)

1) 예시 내용

상가건물을 지을거야. 다 빈 건물이야. 이제 내가 들어갈 때 용도를 알아. 음악실을 지을거면 음악실 용도로 뉴하면 돼. 근데 얘네가 상가 홍보를 해야 되. 고객이 뭘로 들어올지 모르니까 상가 측에서 미리 뉴해서 홍보해야 해. 모델하우스 같은 거. 범용적으로 만들어야 되. 건축업자(클래스 설계하는 사람)가 뉴해서 띄워놔야 돼. 그럼 오브젝트로 설계해야 돼.

2) 정리

핵심 문장
“홍보를 위해 미리 new 해서 띄워놔야 한다”
➡️ 이게 바로
  • UI 프레임워크
  • 서버 컨테이너
  • Spring BeanFactory
  • Servlet Container
의 본질
class Room { Object purpose;// 아직 입점자 모름 }
✔️ new는 했지만 타입은 비어 있음
✔️ 나중에 외부에서 채워짐

4️⃣ 주의 사항

⚠️ Object가 아니라 다른 선택지가 있으면 그걸 써야 함
우선순위:
  1. 인터페이스
  1. 상위 클래스
  1. 제네릭
  1. Object (최후의 수단)
Object o; // ❌ 이유 없으면 피해야 함
T value; // ⭕ 가능하면 제네릭

※ 싱글톤 패턴(Singleton Pattern)

Object 사용이 불가피한 경우 → 1차 개발자가 미리 new 하여 사용자에게 new 제어권이 없을 때
1차 개발자가 미리 new 하는 여러 설계 방식 중 하나가 싱글톤 패턴

1) 싱글톤 패턴이란?

클래스의 인스턴스를 오직 하나만 생성하도록 보장하는 생성 패턴
“프로그램 전체에서 단 하나의 객체만 존재하도록 만든다”

2) 싱글톤이 필요한 이유

✔️ 공통 자원 관리
  • 설정 객체 (Config)
  • 로그 관리자 (Logger)
  • 캐시
  • 스레드 풀
  • DB 커넥션 풀 관리자
👉 여러 개 있으면 오히려 문제

3) 핵심 특징

항목
설명
인스턴스 개수
1개
생성자
private
접근 방법
static 메서드
상태 공유
전역

4) 기본 구조 (Lazy Initialization)

public class Singleton { private static Singleton instance; private Singleton() { } // 외부 new 차단 public static Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } }
Singleton s1 = Singleton.getInstance(); Singleton s2 = Singleton.getInstance(); System.out.println(s1 == s2); // true

5) ⭕ 싱글톤이 적합한 경우

  • 시스템 전체에서 하나여야 하는 객체
  • 상태 공유가 목적
  • 생명주기를 중앙에서 관리해야 할 때

6) ❌ 싱글톤을 피해야 하는 경우

  • 테스트가 중요한 도메인 객체
  • 상태를 많이 가지는 객체
  • 요청마다 다른 인스턴스가 필요한 경우

7) 실습

package ex13; /** * Object로 설계할 수 밖에 없는 이유 */ // 싱글톤 패턴 class 서랍 { // -> 다른 사람이 만들어 놓은 것. Object data; public static 서랍 instance = new 서랍(); // static 때문에 메인 전에 딱 한 번 뜰 수 있고, 더이상 new 할 수 없음 private 서랍() { // -> 생성자에 private 붙으면 아무도 접근 불가/new 제어권 없음/new 불가 } } public class Ge03 { public static void main(String[] args) { 서랍 s = 서랍.instance; // 서랍 클래스에 제네릭 붙여도 new할 수 없기 때문에 소용 없음 s.data = "안녕"; // 항상 다운캐스팅 필요함 String castData = (String) s.data; int len = castData.length(); System.out.println(len); } }
Share article