Q. 자바의 Generic에 대해 설명해주세요.
자바의 Generic은 "이 클래스나 메소드는 나중에 실제로 사용할 때 타입을 정해줄게"라고 하며 작성할 때 타입을 정하지 않는 기능입니다. 클래스나 메소드를 정의할 때 타입 파라미터를 지정하여, 실제 객체를 생성하거나 메소드를 호출할 때 구체적인 타입을 결정할 수 있게 합니다. 이를 통해 형 변환 오류를 줄이고, 컴파일 시점에 오류를 미리 발견하며, 개발자의 생산성을 향상시킬 수 있습니다.
Q. Object를 사용하면 String을 포함한 모든 타입을 다 저장할 수 있을텐데, Generic을 꼭 써야하는 이유가 있나요?
Object를 사용하면 어떤 타입이든 저장할 수 있지만, 꺼낼 때 항상 Object타입으로 반환되기 때문에 추가작업을 위해 명시적 형변환이 필요합니다.
예를 들어, Object 컬렉션에서 Integer를 꺼내서 연산하려면 매번 '(Integer)로 형변환해야 하고, 잘못된 타입이 들어왔을 경우 런타임에 ClassCastException이 발생할 수 있습니다.
List<Object> list = new ArrayList<>();
list.add("Hello");
list.add(123);
//꺼낼 때마다 캐스팅이 필요
Integer num = (Integer) list.get(1); //OK
Integer bad = (Integer) list.get(0); //런타임 에러
제네릭을 사용하면 컴파일 단계에서 "이 컬렉션에는 오직 Integer만 들어올 수 있다"는 제약을 걸어두므로
1. 명시적 캐스팅이 불필요하며
2. 타입 안정성을 확보하고
3. 런타임 오류를 방지할 수 있습니다.
List<Integer> list = new ArrayList<>();
list.add(123);
//list.add("Hello"); //컴파일 에러로 미리 차단
Integer num = list.get(0); //캐스팅 불필요
즉, Object 기반 코드는 유연하지만 ClassCastException같은 위험을 동반합니다.
제네릭을 쓰면 원하는 타입만 저장.조회할 수 있다는 보장이 컴파일러 단계에서 이루어지기 때문에, 코드가 더 안전하고 가독성.유지보수성도 크게 향상됩니다.
Q 추가로 와일드카드에 대해서 설명해주세요.
제네릭 와일드 카드는 제네릭 타입에서 특정 타입을 명시하지 않고, 어떤 타입이 올 수 있는지에 대한 제약을 표현하는 방법입니다.
마치 정규 표현식에서 와일드 카드 문자가 어떤 문자든지 대표하는 것처럼, 제네릭에서도 와일드카드(?)가 다양한 타입을 나타냅니다.
3가지 종류가 있는데
첫번째는 "비제한 와일드 카드"라고 하는 ? 물음표 형태입니다.
이는 어떤 타입이든 받을 수 있지만 구체적인 타입을 알 수 없기 때문에 "읽기(Read)는 가능하지만, 쓰기(Write)는 제한됩니다"
List<Integer>, List<String> 등 어떤 타입의 리스트도 받아서 출력할 수 있습니다. 하지만 타입이 불명확하므로 안전하게 요소를 추가할 수 없습니다.
두번째로는 "상한 제한 와일드 카드"인 ?extends Equipment 입니다.
이는 타입T 또는 그 하위 타입들만 받을 수 있도록 제한합니다.
읽기는 가능하지만, 타입 안정성을 보장할 수 없기 때문에 쓰기는 제한됩니다.
마지막으로 "하한 제한 와일드 카드" 인 ? super Potion은 타입 T 또는 그 상위 타입들만 받을 수 있도록 제한합니다.
쓰기에는 안전하지만, 읽을 때는 구체적인 타입을 알 수 없기 때문에 Object로 밖에 받을 수 없습니다.
이렇게 와일드카드를 쓰면, 메서드가 '아이템을 꺼내기만 할 것인지' 아니면 '아이템을 넣기만 할 것인지'를 선언적으로 구분할 수 있고, 코드의 의도가 훨씬 명확해집니다.
Q. 자바 프로그램이 동작할 때 제네릭은 어떻게 작동하나요? (원시타입으로 제네릭을 쓰는건 왜 안되나요?)
우리가 자바에서 코드를 작성할 때는 제네릭 타입을 사용하지만, 실제로 컴파일되어 실행할 때는 이 타입 정보가 제거됩니다.
이것을 타입 소거라고 하는데
이는 자바의 하위 호환성 때문입니다.
Java5에서 제네릭이 도입됐을 때, 이전 버전의 코드들과 호환성을 유지하면서도 새로운 기능을 추가해야 했기 때문입니다.
그래서 실제로 JVM이 실행할 때는 제네릭 타입 정보가 전부 Object로 변환되고, 필요한 곳에서 자동으로 형변환이 일어납니다.
이때 컴파일러가 미리 타임 체크를 해주기 때문에, 실행 시접에서 형변환 오류가 발생하지는 않습니다.
또한 원시타입은 형변환이 불가능하므로 제네릭으로 원시 타입을 직접 사용할 수는 없습니다.
// 우리가 작성한 코드
ArrayList<String> list = new ArrayList<>();
list.add("Hello");
String str = list.get(0);
// 컴파일 후 실제 실행되는 코드
ArrayList list = new ArrayList();
list.add("Hello");
String str = (String) list.get(0);
Q. 제네릭 클래스와 제네릭 메서드의 차이점은 무엇인가요?
제네릭 클래스는 클래스 전체에 적용되는 타입 파라미터를 가지는 반면, 제네릭 메서드는 메서드에만 적용되는 독립적인 타입 파라미터를 가집니다. 주된 차이점은 적용 범위입니다. 제네릭 클래스는 인스턴스를 생성할 때 타입이 결정되지만, 제네릭 메서드는 해당 메서드를 호출할 때마다 독립적으로 타입을 지정할 수 있습니다.
제네릭 클래스
public class ItemBox<T> {
private List<T> items = new ArrayList<>();
public void addItem(T item) { items.add(item); }
public T getItem(int idx) { return items.get(idx); }
}
ItemBox<Potion> potionBox = new ItemBox<>();
potionBox.addItem(new Potion("체력 회복", 50));
제네릭 메서드
public class Dungeon {
public <T> void printItemName(T item) {
System.out.println("아이템: " + item.toString());
}
}
Dungeon dungeon = new Dungeon();
dungeon.printItemName(new Potion("체력 회복", 50)); // 포션
dungeon.printItemName(new Sword("롱소드")); // 장비
즉, 제네릭 클래스는 클래스 전체가 하나의 타입에 묶여있고, 제네릭 메서드는 그 메서드만 독립적으로 타입을 가집니다.
호출 할 때마다 타입이 바뀔 수 있게 때문에 제네릭 메서드는 특정작업을 여러 타입에 맞춰 깔끔하게 처리할 때 쓰입니다.
컴파일러가 타입 안정성을 체크해주니까 형변환없이 안전하고, 코드도 간결해집니다.
'CS > Java' 카테고리의 다른 글
| 자료형(기본 자료형, 참조 자료형, Wrapper Class, 박싱과 언박싱) (0) | 2025.10.15 |
|---|---|
| Call By Value와 Call By Reference (0) | 2025.10.14 |