01. 제네릭(Generic)
- 컴파일 시점에 데이터 타입을 미리 지정할 수 있는 기능
- 객체의 타입을 명확히 지정하여, 잘못된 타입의 데이터를 다루는 실수를 방지할 수 있음.
- 제네릭 문법: 보통 **꺾쇠괄호(<>)**를 사용해 타입 매개변수를 정의
class 클래스명<T> { /* 제네릭 클래스 정의 */ }
interface 인터페이스명<T> { /* 제네릭 인터페이스 정의 */ }
*T는 Type의 약자로, 보통 관습적으로 사용되며 다른 이름도 가능
02. 종류
: 제네릭 클래스, 제네릭 메서드, 그리고 와일드카드(Generic Wildcards)로 나뉨.
(1) 제네릭 클래스: 클래스가 다룰 데이터 타입을 클래스 정의 시점이 아닌 객체 생성 시점에 결정할 수 있음.
*정의*
class Box<T> {
private T item;
public void set(T item) {
this.item = item;
}
public T get() {
return item;
}
}
*사용*
Box<String> stringBox = new Box<>();
stringBox.set("Hello");
String value = stringBox.get();
Box<Integer> intBox = new Box<>();
intBox.set(123);
Integer number = intBox.get();
(2) 제네릭 메서드: 메서드에 사용되는 타입을 메서드 호출 시점에 지정할 수 있음( 클래스 자체가 제네릭이 아닐 때도 제네릭 메서드를 정의할 수 있음)
*정의*
class Utils {
public static <T> T getMiddle(T[] array) {
return array[array.length / 2];
}
}
*사용*
String[] names = {"Alice", "Bob", "Charlie"};
String middleName = Utils.getMiddle(names); // "Bob"
Integer[] numbers = {1, 2, 3, 4};
Integer middleNumber = Utils.getMiddle(numbers); // 2
(3) 와일드카드(Generic Wildcards): ?를 사용하여 알 수 없는 타입을 표현 (제네릭 타입의 범위 제한에 사용됨)
: 종류 (Unbounded Wildcard(?), Bounded Wildcard(<? extends T> or <? super T>)
- Unbounded Wildcard (?): 아무 타입이나 허용.
public void printList(List<?> list) {
for (Object obj : list) {
System.out.println(obj);
}
}
- Bounded Wildcard
*상한 제한 (<? extends T>): T와 T의 하위 클래스만 허용*
public void printList(List<? extends Number> list) {
for (Number num : list) {
System.out.println(num);
}
}
*하한 제한 (<? super T>): T와 T의 상위 클래스만 허용*
public void addToList(List<? super Integer> list) {
list.add(42);
}
03. 특징
(1) 타입 안정성(Type Safety)
예시(ArrayList) | |
제네릭을 사용하지 않을 경우 | ArrayList는 기본적으로 다양한 객체(Object)를 저장할 수 있음. 만약 제네릭을 사용하지 않으면, 객체를 꺼낼 때 **타입 캐스팅(type casting)**을 해야 하고, 런타임에 잘못된 타입이 들어가면 ClassCastException이 발생할 수 있음. |
제네릭을 사용할 경우 | 컴파일 타임에 데이터 타입을 지정하여 잘못된 타입의 객체가 추가되지 않도록 방지함. 따라서 코드에서 명시적으로 캐스팅할 필요가 없어지고,런타임 오류를 줄일 수 있음. |
* 제네릭을 사용하지 않을 경우*
ArrayList list = new ArrayList();
list.add("String");
list.add(10); // 정수 추가
String s = (String) list.get(0); // 캐스팅 필요
Integer i = (Integer) list.get(1); // 가능
String error = (String) list.get(1); // 런타임 오류 발생 (ClassCastException)
* 제네릭을 사용할 경우*
ArrayList<String> list = new ArrayList<>();
list.add("String");
// list.add(10); // 컴파일 오류 발생!
String s = list.get(0); // 캐스팅 불필요
(2) 코드 재사용성(Code Reusability)
: 제네릭을 사용하면 다양한 데이터 타입에 대해 하나의 클래스로 처리할 수 있음.
ArrayList<String> stringList = new ArrayList<>();
ArrayList<Integer> intList = new ArrayList<>();
ArrayList는 제네릭 클래스로 구현되어 있어, 특정 타입으로 제한하지 않고 모든 데이터 타입에 대해 동일한 로직으로 동작할 수 있고, 따로 구현할 필요 없이 원하는 타입에 따라 ArrayList를 재사용할 수 있음.
(3) 가독성과 유지보수성
- 코드 가독성: 제네릭을 사용하면 ArrayList가 다루는 데이터 타입이 명확하므로, 코드를 읽는 사람에게 의도를 쉽게 전달할 수 있음.
- 유지보수성: 잘못된 데이터 타입이 들어가거나 꺼내는 오류를 컴파일 시점에 확인할 수 있으므로, 버그를 조기에 발견하고 수정할 수 있음.
(4) 타입 변환 비용 제거
: 제네릭을 사용하면 명시적인 타입 캐스팅이 필요 없으므로, 타입 캐스팅에 소모되는 시간적 비용을 줄일 수 있. 이는 특히 성능이 중요한 상황에서 유리함.
04. 제네릭 제한사항
1. 기본 타입 사용 불가: 제네릭은 참조 타입만 사용 가능, 기본 타입(int, double...)은 래퍼 클래스(Integer, Double...)로 사용.
List<int> list; // 불가능
List<Integer> list; // 가능
2. 런타임 시 타입 소거(Type Erasure): 컴파일 시 타입 검사를 수행, 런타임 시에는 타입 정보 제거( => 제네릭 타입은 컴파일 후 Object로 처리)
3. 정적 멤버에서 제네릭 사용 불가: 정적 멤버는 클래스 수준에서 공유되므로, 제네릭 타입 매개변수 사용 불가
class Example<T> {
static T staticField; // 불가능
}
'JAVA 기초' 카테고리의 다른 글
[JAVA 기초] JAVA의 정석 - Ch.05 배열(Array) (정리) (2) | 2025.01.15 |
---|---|
[JAVA 기초] JAVA의 정석 - Ch.04 조건문과 반복문 (정리) (1) | 2025.01.15 |
[JAVA 기초] JAVA의 정석 - Ch.03 연산자(Operator) (정리) (0) | 2025.01.15 |
[JAVA 기초] JAVA의 정석 - Ch.02 변수(Variable) (정리) (0) | 2025.01.15 |
[JAVA 기초] JAVA의 정석 - Ch.01 자바를 시작하기 전에 (정리) (3) | 2025.01.15 |