제네릭스
2025. 2. 26. 17:32ㆍJava/Java 문법
1. 제네릭스(Generics)
1-1. 제네릭스란?
- 자바에서 클래스나 메서드가 다룰 데이터 타입을 컴파일 시점에 지정할 수 있도록 하는 기능
- 쉽게 말해서, 타입을 변수처럼 사용할 수 있게 해주는 것
- 제네릭스를 활용하는 제네릭 클래스는 제네릭 타입(T, E, K, V)을 활용하여 하나의 클래스로 해당 제네릭 타입에 변화를 줘서 제네릭 클래스의 인스턴스를 다양한 타입을 지닌 인스턴스로 활용할 수 있다.
- 제네릭 클래스와 제네릭 메서드를 사용하면 여러 타입을 유연하게 처리 가능
- 제네릭 선언은 다이아몬드 연산자 <>를 이용하여 작성하고 <>안에는 참조형을 선언해줘야 한다
1-2. 제네릭 클래스
// 제네릭 클래스 정의
class Box<T> { // T는 타입 변수 (T, E, K, V 등 아무 이름이나 가능)
private T value;
public void set(T value) {
this.value = value;
}
public T get() {
return value;
}
}
public class Main {
public static void main(String[] args) {
//제네릭 클래스 Box를 인스턴스화할때 타입 T를 String으로 생성
Box<String> stringBox = new Box<>();
stringBox.set("Hello");
System.out.println(stringBox.get()); // Hello
//제네릭 클래스 Box를 인스턴스화할때 타입 T를 Integer으로 생성
Box<Integer> intBox = new Box<>(); //
intBox.set(123);
System.out.println(intBox.get()); // 123
}
}
1-3. 제네릭 메서드
class Util {
//제네릭 메서드 선언
public static <T> void print(T data) {
System.out.println(data);
}
}
public class Main {
public static void main(String[] args) {
//한 가지 메서드로 여러 타입을 처리할 수 있어서 코드가 깔끔하다.
Util.print("Hello"); // 매개변수를 String 타입으로 사용
Util.print(123); // 매개변수를 Integer 타입으로 사용
Util.print(3.14); // 매개변수를 Double 타입으로 사용
}
}
2. 제네릭스를 사용하는 이유
2-1. 다양한 타입을 사용할 수 있음 → 재사용성 UP
- List<T> 같은 제네릭 클래스를 만들면, List<String>, List<Integer> 등 다양한 타입을 지정해서 사용 가능
- 같은 코드로 여러 타입을 다룰 수 있으니까 코드 재사용성이 증가한다.
//List를 제네릭 인터페이스로 정의
public interface List<E> extends Collection<E> {
boolean add(E e);
E get(int index);
// ...
}
//같은 List클래스를 다양한 타입으로 활용 가능
List<String> stringList = new ArrayList<>();
List<Integer> intList = new ArrayList<>();
2-2. 타입 안정성 확보 + 형 변환 불필요
- 타입 안정성 확보: 제네릭을 사용하면 컴파일 타임에 타입 체크가 돼서 잘못된 타입을 넣는 실수를 방지할 수 있음
- 형변환 불필요: 명확한 타입을 지정해서 사용하기 때문에 데이터를 꺼낼 때도 형 변환이 필요 없어서 코드가 깔끔해짐.
// 1. 제네릭 미사용 (Object 사용)
List list = new ArrayList();
list.add("Hello");
list.add(123); // 다른 타입도 들어감 -> 문제 발생 가능
String str = (String) list.get(0); // 데이터 꺼낼 때 마다 형 변환 필요
// 2. 제네릭 사용
//String타입만 저장 가능한 리스트
List<String> list = new ArrayList<>();
list.add("Hello");
//list.add(123); // 다른 타입이 들어갈 경우 컴파일 에러! -> 안정적인 코드 작성 가능
String str = list.get(0); // 명확한 타입이 지정되어 있기 때문에 형 변환 필요 없음
3. 와일드 카드
- 제네릭 클래스의 인스턴스를 유연하게 활용하기 위한 문법
- 메소드의 매개변수로 받을 시 타입을 원하는 만큼으로 제한하는 것
- 불특정한 제네릭 클래스 타입을 조금 더 활용할 수 있다.
- 종류
- <?>: 모든 타입을 허용하는 와일드 카드
- <? extends T>: T 타입 또는 T의 하위 타입을 허용하는 와일드 카드
- <? super T>: T 타입 또는 T의 상위 타입을 허용하는 와일드 카드
/* 토끼농장에 있는 토끼가 어떤 토끼이던 상관 없다. */
public void anyType(RabbitFarm<?> farm) {
farm.getAnimal().cry();
}
/* 토끼농장의 토끼는 Bunny이거나 그 후손 타입으로 만들어진 토끼농장만 매개변수로 사용 가능 */
public void extendsType(RabbitFarm<? extends Bunny> farm) {
farm.getAnimal().cry();
}
/* 토끼농장의 토끼는 Bunny이거나 그 부모 타입으로 만들어진 토끼농장만 매개변수로 사용 가능 */
public void superType(RabbitFarm<? super Bunny> farm) {
farm.getAnimal().cry();
}