목차
Java Generic(제네릭) 란?
1. Generic(제네릭)이란?
Java에서 **제네릭(Generic)**은 클래스나 메서드에서 사용할 데이터 타입을 미리 지정하지 않고, 나중에 필요할 때 지정할 수 있도록 하는 기능이다. 즉, 컴파일 시점에서 타입을 지정하여 코드의 재사용성을 높이고, 타입 안정성을 보장할 수 있다.
제네릭을 사용하면 컴파일 시 타입 체크가 가능하여 실행 시 발생할 수 있는 ClassCastException과 같은 오류를 방지할 수 있다.
2. Generic의 장점
- 타입 안정성(Type Safety): 컴파일 시점에서 타입을 체크하여 잘못된 타입 사용을 방지할 수 있다.
- 코드 재사용성 증가: 하나의 클래스 또는 메서드를 다양한 타입으로 활용할 수 있다.
- 형 변환(Casting) 필요 없음: 불필요한 형 변환을 줄여 코드의 가독성을 향상시킨다.
- 유지보수 용이: 코드의 일관성이 유지되어 디버깅과 유지보수가 용이하다.
3. Generic 사용 방법
1) 클래스에서 제네릭 사용
제네릭을 사용하여 클래스에서 다양한 타입을 처리할 수 있도록 만들 수 있다.
class Box<T> {
private T item;
public void setItem(T item) { this.item = item; }
public T getItem() { return item; }
}
사용 예제
public class Main {
public static void main(String[] args) {
Box<String> stringBox = new Box<>();
stringBox.setItem("Hello Generic");
System.out.println(stringBox.getItem()); // 출력: Hello Generic
Box<Integer> intBox = new Box<>();
intBox.setItem(100);
System.out.println(intBox.getItem()); // 출력: 100
}
}
위 예제에서 Box<T> 클래스는 T 타입의 데이터를 저장할 수 있으며, String 또는 Integer 같은 특정 타입으로 사용할 수 있다.
2) 메서드에서 제네릭 사용
메서드에서도 제네릭을 사용할 수 있으며, 다양한 타입의 값을 처리할 수 있다.
public class Util {
public static <T> void printItem(T item) {
System.out.println("Item: " + item);
}
}
사용 예제
public class Main {
public static void main(String[] args) {
Util.printItem("Generic Method"); // 출력: Item: Generic Method
Util.printItem(123); // 출력: Item: 123
}
}
제네릭 메서드는 <T>를 사용하여 다양한 타입을 인자로 받을 수 있으며, printItem 메서드는 어떤 타입이든 매개변수로 받아 출력할 수 있다.
4. Generic 타입
1) 여러 개의 타입 매개변수 사용
제네릭 클래스에서 여러 개의 타입 매개변수를 사용할 수도 있다.
class Pair<K, V> {
private K key;
private V value;
public Pair(K key, V value) {
this.key = key;
this.value = value;
}
public K getKey() { return key; }
public V getValue() { return value; }
}
사용 예제
public class Main {
public static void main(String[] args) {
Pair<String, Integer> pair = new Pair<>("Age", 25);
System.out.println(pair.getKey() + ": " + pair.getValue()); // 출력: Age: 25
}
}
이처럼 Pair<K, V> 클래스를 활용하면 서로 다른 두 개의 타입을 하나의 객체로 다룰 수 있다.
2) 제네릭 인터페이스 사용
인터페이스에서도 제네릭을 적용할 수 있으며, 이를 구현하는 클래스는 특정 타입을 사용할 수 있다.
interface Container<T> {
void add(T item);
T get();
}
class StringContainer implements Container<String> {
private String item;
public void add(String item) { this.item = item; }
public String get() { return item; }
}
사용 예제
public class Main {
public static void main(String[] args) {
StringContainer container = new StringContainer();
container.add("Hello Interface");
System.out.println(container.get()); // 출력: Hello Interface
}
}
Container<T> 인터페이스를 구현한 StringContainer 클래스는 String 타입만 처리하도록 제한된다.
5. Generic 선언 및 생성 방법
1) 클래스에서 선언
class Sample<T> {
private T value;
public Sample(T value) { this.value = value; }
public T getValue() { return value; }
}
이 클래스는 T 타입의 데이터를 저장하고 반환할 수 있으며, 특정 타입을 지정하여 사용할 수 있다.
2) 메서드에서 선언
public class Utility {
public static <T> T getFirstElement(T[] array) {
return array.length > 0 ? array[0] : null;
}
}
이 메서드는 배열의 첫 번째 요소를 반환하는데, 입력되는 배열의 타입에 따라 유연하게 작동한다.
3) 인터페이스에서 선언
interface DataProcessor<T> {
void process(T data);
}
이 인터페이스는 T 타입의 데이터를 처리하는 기능을 제공하며, 다양한 타입에 맞게 구현될 수 있다.
6. 제한된 Generic(제네릭)과 와일드 카드
1) 상한 제한 (extends)
제네릭 타입 매개변수에 특정 타입의 하위 클래스만 허용하고 싶을 때 extends 키워드를 사용한다.
class NumberBox<T extends Number> {
private T number;
public NumberBox(T number) { this.number = number; }
public T getNumber() { return number; }
}
사용 예제
public class Main {
public static void main(String[] args) {
NumberBox<Integer> intBox = new NumberBox<>(10);
NumberBox<Double> doubleBox = new NumberBox<>(3.14);
System.out.println("Integer Box: " + intBox.getNumber());
System.out.println("Double Box: " + doubleBox.getNumber());
}
}
NumberBox<T extends Number>는 Number의 하위 클래스(Integer, Double 등)만 사용 가능하다. NumberBox<String>과 같이 Number의 하위 클래스가 아닌 타입을 지정하면 컴파일 오류가 발생한다.
2) 하한 제한 (super)
제네릭에서 특정 타입의 상위 클래스만 허용하려면 super 키워드를 사용한다.
class DataProcessor<T> {
public void processData(T data) {
System.out.println("Processing: " + data);
}
}
public class Main {
public static void processNumberBox(DataProcessor<? super Integer> processor, Integer data) {
processor.processData(data);
}
public static void main(String[] args) {
DataProcessor<Number> numberProcessor = new DataProcessor<>();
processNumberBox(numberProcessor, 100); // 가능
}
}
위 코드에서 DataProcessor<? super Integer>는 Integer의 상위 타입(Number, Object 등)만 사용할 수 있다.
3) 와일드 카드 (?)
와일드 카드는 제네릭 타입을 유연하게 사용할 수 있도록 한다. 다음과 같이 세 가지 형태로 사용된다.
(1) 제한 없음 (? 만 사용)
public static void printList(List<?> list) {
for (Object item : list) {
System.out.println(item);
}
}
List<?>는 어떠한 타입의 리스트든 허용한다.
List<String> strList = Arrays.asList("A", "B", "C");
List<Integer> intList = Arrays.asList(1, 2, 3);
printList(strList);
printList(intList);
(2) 상한 제한 (extends)
public static void printNumbers(List<? extends Number> list) {
for (Number num : list) {
System.out.println(num);
}
}
List<? extends Number>는 Number와 그 하위 클래스(Integer, Double 등)만 허용한다.
List<Integer> intList = Arrays.asList(1, 2, 3);
List<Double> doubleList = Arrays.asList(1.1, 2.2, 3.3);
printNumbers(intList);
printNumbers(doubleList);
(3) 하한 제한 (super)
public static void addNumbers(List<? super Integer> list) {
list.add(100);
}
List<? super Integer>는 Integer의 상위 타입(Number, Object)만 허용한다.
List<Number> numList = new ArrayList<>();
addNumbers(numList);
와일드 카드를 사용하면 유연한 타입 매칭이 가능하지만, 읽기/쓰기 제약이 존재하므로 상황에 맞게 사용해야 한다.
위 내용을 통해 제한된 제네릭과 와일드 카드에 대한 개념과 사용법을 자세히 이해할 수 있다. 추가적으로 궁금한 점이 있으면 언제든지 질문해주세요! 😊
'이직&취업 > Java 기초 상식' 카테고리의 다른 글
JAVA thread safe 란? (12) | 2025.03.21 |
---|---|
브라우저에 www.naver.com을 입력하면??? (2) | 2025.03.19 |
JAVA 버전 별 특징 및 주요 추가 기능 (5) | 2025.03.17 |
JVM(Java Virtual Machine)과 자바 가비지 컬렉션(GC) (4) | 2025.03.17 |
String, StringBuffer, StringBuilder 비교 (5) | 2025.03.14 |