**제네릭(Generic)**은 자바의 기능으로, 클래스나 메서드에서 사용할 데이터 타입을 외부에서 지정할 수 있게 해줍니다. 즉, 제네릭을 사용하면 코드의 재사용성을 높이고, 컴파일 시점에 타입 안정성을 보장할 수 있습니다.
제네릭을 사용하면 다양한 데이터 타입을 처리할 수 있는 클래스를 정의하면서도, 타입 안전성을 유지할 수 있습니다. 이는 특히 컬렉션 클래스에서 많이 사용됩니다.
1. 제네릭의 기본 개념
제네릭 클래스
제네릭 클래스를 사용하면, 하나의 클래스가 여러 타입을 처리할 수 있습니다. 예를 들어, 데이터 타입을 명시하지 않고 클래스에 외부에서 타입을 지정할 수 있습니다.
public class Box<T> {
private T item; // 타입 T는 외부에서 지정됩니다.
public void setItem(T item) {
this.item = item;
}
public T getItem() {
return item;
}
}
• T는 타입 매개변수로, 실제로는 데이터 타입을 의미합니다. 여기서 T는 특정 타입으로 대체됩니다.
• 예를 들어, T가 String이 되면 **Box<String>**이 되고, T가 Integer가 되면 **Box<Integer>**가 됩니다.
제네릭 클래스 사용 예시
public class Main {
public static void main(String[] args) {
// String 타입의 제네릭 클래스 사용
Box<String> stringBox = new Box<>();
stringBox.setItem("Hello");
System.out.println(stringBox.getItem()); // 출력: Hello
// Integer 타입의 제네릭 클래스 사용
Box<Integer> intBox = new Box<>();
intBox.setItem(123);
System.out.println(intBox.getItem()); // 출력: 123
}
}
• 이처럼 **Box<String>**과 **Box<Integer>**에서 동일한 클래스를 사용하면서도 다른 데이터 타입을 처리할 수 있습니다.
2. 제네릭 메서드
제네릭은 메서드에서도 사용할 수 있습니다. 제네릭 메서드를 사용하면 메서드가 처리하는 데이터 타입을 메서드를 호출할 때 지정할 수 있습니다.
제네릭 메서드 정의
public class GenericMethodExample {
// 제네릭 메서드 정의
public static <T> void printArray(T[] array) {
for (T element : array) {
System.out.println(element);
}
}
}
• **<T>**는 메서드에서 사용되는 타입 매개변수이며, 메서드를 호출할 때 데이터 타입이 결정됩니다.
• 위의 예시에서 printArray 메서드는 다양한 타입의 배열을 처리할 수 있습니다.
3. 제네릭의 장점
1. 타입 안전성: 제네릭을 사용하면 컴파일 시점에 타입을 체크할 수 있으므로, 런타임에 잘못된 타입을 사용하는 오류를 방지할 수 있습니다.
2. 재사용성: 제네릭을 사용하면 하나의 클래스나 메서드로 다양한 타입을 처리할 수 있으므로, 코드의 재사용성이 높아집니다.
3. 타입 캐스팅 제거: 제네릭을 사용하면 명시적인 타입 캐스팅이 필요 없으므로, 코드가 더 간결하고 안전해집니다.
4. 제네릭의 제한 사항
1. 기본 타입 사용 불가: 제네릭은 기본 데이터 타입(primitive type)(예: int, char)을 사용할 수 없습니다. 대신 래퍼 클래스(예: Integer, Character)를 사용해야 합니다.
// 잘못된 사용 (기본 타입 사용 불가)
// Box<int> intBox = new Box<>(); // 오류 발생
// 올바른 사용 (래퍼 클래스 사용)
Box<Integer> intBox = new Box<>();
2. 정적(static) 변수 사용 불가: 제네릭 타입은 정적 필드로 사용할 수 없습니다. 정적 필드는 클래스 수준에서 공유되므로, 제네릭 타입을 사용하는 것은 불가능합니다.
public class Box<T> {
// static T item; // 오류 발생
}
3. 런타임에 타입 정보 손실: 제네릭은 컴파일 시점에만 타입 검사를 수행하며, 런타임에는 타입 정보가 제거됩니다. 이를 **타입 소거(type erasure)**라고 합니다.
5. 와일드카드(Generic Wildcards)
제네릭에서는 와일드카드를 사용하여 보다 유연한 타입을 처리할 수 있습니다. 와일드카드는 **?**로 표기되며, 특정 범위 내에서 제네릭 타입을 정의할 수 있습니다.
? (Unbounded Wildcard)
• 와일드카드 ?는 모든 타입을 허용합니다.
public void printBox(Box<?> box) {
System.out.println(box.getItem());
}
• 이 메서드는 **어떤 타입의 Box**든지 받아서 출력할 수 있습니다.
<? extends T> (Upper Bounded Wildcard)
• <? extends T>는 T 또는 T의 하위 클래스만 허용합니다.
public void printNumbers(Box<? extends Number> box) {
System.out.println(box.getItem());
}
• 이 메서드는 **Number 클래스와 그 하위 클래스(Integer, Double 등)**를 받을 수 있습니다.
<? super T> (Lower Bounded Wildcard)
• <? super T>는 T 또는 T의 상위 클래스만 허용합니다.
public void addToList(List<? super Integer> list) {
list.add(123); // Integer나 그 상위 클래스(List<Number> 등)에만 추가 가능
}
• 이 메서드는 Integer 클래스와 그 상위 클래스에 값을 추가할 수 있습니다.
6. 제네릭 사용 예시 (자주 사용되는 컬렉션과 제네릭)
자바에서 제네릭은 컬렉션 클래스에서 가장 많이 사용됩니다. 예를 들어, **List, Map, Set**과 같은 컬렉션 클래스는 제네릭을 사용하여 특정 타입의 데이터를 처리할 수 있습니다.
List와 제네릭
import java.util.ArrayList;
import java.util.List;
public class Main {
public static void main(String[] args) {
// String 타입을 저장하는 리스트
List<String> list = new ArrayList<>();
list.add("Apple");
list.add("Banana");
for (String item : list) {
System.out.println(item);
}
}
}
• 제네릭을 사용하지 않을 경우에는 타입 캐스팅을 해야 하므로, 런타임에 타입 오류가 발생할 가능성이 있습니다. 제네릭을 사용하면 컴파일 시점에 타입이 체크되므로, 더 안전한 코드를 작성할 수 있습니다.
결론
• **제네릭(Generic)**은 자바에서 클래스나 메서드가 다양한 타입을 처리할 수 있도록 하며, 코드의 타입 안전성을 높여줍니다.
• 제네릭을 사용하면 타입 캐스팅이 필요 없고, 컴파일 시점에 타입 체크가 가능하여 안정성을 보장할 수 있습니다.
• 와일드카드를 통해 제네릭 타입을 더 유연하게 정의할 수 있습니다.
• 제네릭은 특히 컬렉션 클래스에서 많이 사용되며, 코드 재사용성을 극대화할 수 있습니다.
'Java' 카테고리의 다른 글
[Java] Interface (0) | 2024.09.28 |
---|---|
[Java]Generic(Interface에서의 활용) (0) | 2024.09.28 |
[JAVA] Static (0) | 2024.09.24 |
[JAVA] 빌더(builder) (0) | 2024.09.24 |
[JAVA] 예외처리(Exception) (0) | 2024.09.24 |