주의
- 본 게시글은 Claude로 작성되었습니다. 잘못된 정보가 있을 수 있습니다.
개요
자바 제네릭스(Generics)는 Java 5부터 도입된 강력한 기능으로, 컬렉션 등의 클래스에서 다양한 타입을 안전하게 사용할 수 있게 해줍니다. 이 문서에서는 제네릭스의 기본 개념부터 실제 사용법까지 초보자도 쉽게 이해할 수 있도록 상세히 설명합니다.
제네릭스란?
제네릭스는 클래스나 메서드에서 사용할 내부 데이터 타입을 컴파일 시에 미리 지정하는 방법입니다. 이를 통해 다음과 같은 이점을 얻을 수 있습니다:
- 타입 안정성 향상
- 코드 재사용성 증가
- 타입 캐스팅 감소
제네릭스 사용 전과 후 비교
제네릭스를 사용하기 전:
ArrayList list = new ArrayList();
list.add("문자열");
list.add(100); // 정수도 추가 가능
String str = (String) list.get(0); // 타입 캐스팅 필요
Integer num = (Integer) list.get(1); // 타입 캐스팅 필요제네릭스 사용 후:
ArrayList<String> list = new ArrayList<>();
list.add("문자열");
// list.add(100); // 컴파일 에러 발생
String str = list.get(0); // 타입 캐스팅 불필요제네릭스 상세 설명
제네릭 클래스 정의
제네릭 클래스는 다음과 같이 정의합니다:
public class Box<T> {
private T content;
public void set(T content) {
this.content = content;
}
public T get() {
return content;
}
}여기서 T는 타입 파라미터로, 실제 사용 시 구체적인 타입으로 대체됩니다.
제네릭 클래스 사용
정의한 제네릭 클래스는 다음과 같이 사용합니다:
Box<String> stringBox = new Box<>();
stringBox.set("안녕하세요");
String greeting = stringBox.get(); // 타입 캐스팅 불필요
Box<Integer> integerBox = new Box<>();
integerBox.set(100);
int number = integerBox.get(); // 타입 캐스팅 불필요제네릭 메서드
제네릭은 메서드 레벨에서도 사용할 수 있습니다:
public static <E> void printArray(E[] array) {
for (E element : array) {
System.out.print(element + " ");
}
System.out.println();
}사용 예:
String[] stringArray = {"Hello", "World"};
Integer[] integerArray = {1, 2, 3, 4, 5};
printArray(stringArray); // 출력: Hello World
printArray(integerArray); // 출력: 1 2 3 4 5와일드카드
와일드카드(?)를 사용하면 알 수 없는 타입을 표현할 수 있습니다:
public static void printList(List<?> list) {
for (Object elem : list) {
System.out.print(elem + " ");
}
System.out.println();
}사용 예:
List<Integer> intList = Arrays.asList(1, 2, 3);
List<String> strList = Arrays.asList("Hello", "World");
printList(intList); // 출력: 1 2 3
printList(strList); // 출력: Hello World사용 예제
제네릭을 활용한 간단한 스택 구현
public class Stack<E> {
private ArrayList<E> elements;
public Stack() {
this.elements = new ArrayList<>();
}
public void push(E item) {
elements.add(item);
}
public E pop() {
if (isEmpty()) {
throw new EmptyStackException();
}
return elements.remove(elements.size() - 1);
}
public boolean isEmpty() {
return elements.isEmpty();
}
}사용 예:
Stack<String> stringStack = new Stack<>();
stringStack.push("첫 번째");
stringStack.push("두 번째");
System.out.println(stringStack.pop()); // 출력: 두 번째
System.out.println(stringStack.pop()); // 출력: 첫 번째
Stack<Integer> intStack = new Stack<>();
intStack.push(1);
intStack.push(2);
System.out.println(intStack.pop()); // 출력: 2
System.out.println(intStack.pop()); // 출력: 1참고 자료
FAQ
Q: 제네릭스를 사용하는 주된 이유는 무엇인가요?
- 제네릭스를 사용하면 컴파일 시점에 타입 안정성을 보장할 수 있습니다. 이는 런타임 에러를 줄이고, 코드의 가독성과 재사용성을 높여줍니다.
Q: 제네릭스의 타입 파라미터로 기본 타입(int, double 등)을 사용할 수 있나요?
- 아니요, 제네릭스의 타입 파라미터로는 참조 타입만 사용할 수 있습니다. 기본 타입을 사용하려면 해당 타입의 래퍼 클래스(Integer, Double 등)를 사용해야 합니다.
Q: 제네릭 메서드와 제네릭 클래스의 차이점은 무엇인가요?
- 제네릭 클래스는 클래스 전체에 대해 타입 파라미터를 적용하지만, 제네릭 메서드는 해당 메서드에 대해서만 타입 파라미터를 적용합니다. 제네릭 메서드는 non-generic 클래스 내에서도 사용할 수 있습니다.
관련 질문 및 추가 정보
- 제네릭스와 상속: 제네릭 타입의 상속 관계에 대해 더 알아보세요.
- 타입 경계(Bounded Type Parameters): 특정 타입의 서브클래스로 타입 파라미터를 제한하는 방법을 학습하세요.
- 타입 추론: Java 7 이후 도입된 다이아몬드 연산자
<>를 통한 타입 추론에 대해 알아보세요. - 제네릭스의 타입 소거(Type Erasure): 컴파일 후 제네릭 정보가 어떻게 처리되는지 이해하세요.
- 제네릭스와 컬렉션 프레임워크: Java의 컬렉션 프레임워크에서 제네릭스가 어떻게 활용되는지 살펴보세요.