개발 기록
>[Java] 제네릭(Generics) - 기본 개념과 사용법 본문

■
1. 개념
Java 제네릭(Generics)은 타입 안정성(type safety)을 제공하고 재사용 가능한 코드를 작성할 수 있도록 하는 기능이다.
리스트를 생성할 때 해당 리스트가 문자열(String) 요소만을 저장할 수 있도록 타입을 제한 하기 위해서, 아래처럼 생성 할것이다.
List<String> textList 에서 <String>이 바로 제네릭 타입 매개 변수로, 실 타입 매개변수라고 한다. 리스트가 어떤 타입의 요소를 저장할지 지정하며, 컴파일러가 요소의 타입이 'String' 인지 체크하기 때문에 다른 타입의 데이터를 넣을시 컴파일 오류가 발생한다.
List<String> textList = new ArrayList<>();
Java의 컬렉션 프레임워크는 제네릭을 기반으로 설계되어 있는데 List 인터페이스를 봐보면 알 수 있다. List 인터페이스에 선언되어 있는 List<E>의 E 형식 타입 매개변수라고 한다.

☞ 실 타입 매개변수, 형식 타입 매개변수는 제네릭 클래스 또는 인터페이스를 정의할 때 타입을 파라미터화하는데 사용한다.
☞ 형식 타입 매개변수: 제네릭 클래스 또는 인터페이스의 선언에서 사용되며, 타입을 추상화 ex. <T>, <E>, <K, V>
☞ 실 타입 매개변수: 실제로 사용할 때 지정되는 구체적인 타입
개념은 알겠고 그렇다면 왜 제네릭을 사용해야 할까?
※ 제네릭으로 얻는 이점
① 타입 안정성: 제네릭은 컴파일 시에 타입 검사를 수행하여 타입 불일치로 인한 런타임 오류를 사전에 방지한다.
② 코드 재사용성 및 유연성: 다양한 타입에 인자를 처리할 수 있으므로 동일한 로직을 재사용할 수 있다.
③ 가독성: 타입캐스팅 같은 명시적인 형 변환을 줄일 수 있다.
※ 제네릭의 한계
① 타입 소거로 인한 제한: 타입 소거로 인해 런타임 시 제네릭 타입의 실제 타입 정보를 알기 어렵다.
② 원시 타입과의 호환성: 제네릭 코드는 원시 타입(raw type)과 호환성이 있어 제네릭 타입에서 타입 안전성을 보장하지 못할 수 있다.
■
2. 사용법
(1) 매개변수화 타입 class<T>, interface<T>
매개변수화 타입은 제네릭 타입의 한 종류로, 타입 매개변수를 포함하는 클래스 또는 인터페이스를 말한다.
▶ 형식 타입 매개변수
사용할 타입을 추상적으로 나타내며, 타입 매개변수는 보통 T, E, K, V 등으로 표현한다.
// Box 클래스가 어떤 타입의 객체를 다룰지 추상적으로 나타낸다.
public class Box<T> {
private T content;
public T getContent() {
return content;
}
public void setContent(T content) {
this.content = content;
}
}
▶ 타입 매개변수
: 제네릭 클래스 또는 인터페이스의 형식 타입 매개변수에 실제 타입을 바인딩한다. 제네릭 클래스 또는 인터페이스를 인스턴스화할 때 구체적인 타입을 지정한다.
public class Main {
public static void main(String[] args) {
// Box<String> 형식 타입 매개변수 T에 대해 실 타입 매개변수를 String을 지정하여 Box 클래스를 인스턴스화한다.
Box<String> stringBox = new Box<>("Hello");
System.out.println(stringBox.getContent()); // 출력: Hello
// Box<Integer>는 형식 타입 매개변수 T에 대해 실 타입 매개변수를 Integer로 지정하여 Box 클래스를 인스턴스화한다.
Box<Integer> intBox = new Box<>(123);
System.out.println(intBox.getContent()); // 출력: 123
}
}
(2) 제네릭 메서드<T> R method(T t)
메서드 선언 시에 타입 매개변수를 사용하여 여러 타입의 인자를 처리할 수 있는 메서드이다.
☞ <T>: 메서드에 사용되는 타입 매개변수로, 임의의 대문자 알파벳으로 표시
☞ R: 반환할 값의 타입
☞ T parameter: 제네릭 메서드의 매개변수
// 이 메서드는 입력된 value를 String으로 변환하여 반환한다.
public class GenericMethodExample {
// 제네릭 메서드 선언
// <T, R> 두 개의 타입 매개변수를 사용하여 입력값과 반환값의 타입을 독립적으로 다룬다.
public static <T, R> R convert(T value) {
// value를 String으로 변환하여 (R) 형변환을 통해 반환
return (R) String.valueOf(value);
}
public static void main(String[] args) {
// 제네릭 메서드를 사용하여 타입 변환하기
Integer intValue = 100;
String stringValue = convert(intValue);
System.out.println("Converted String value: " + stringValue);
Double doubleValue = 3.14;
String doubleStringValue = convert(doubleValue);
System.out.println("Converted String value: " + doubleStringValue);
}
}

* 참고: 리턴타입을 알아내는 방법(=매개변수도 마찬가지)
1. 명시적인 타입 지정: 메서드 이름 뒤에 <타입>을 붙여서 반환 타입 지정
String result = GenericMethodExample.<Integer, String>convert(123); // String 타입으로 반환 받음
2. 타입 추론: Java 컴파일러는 제네릭 메서드 호출 시 입력 인자를 보고 타입을 추론할 수 있기 때문에 명시적으로 지정할 필요가 없다.
// Integer 타입의 인자를 전달하면 컴파일러는 T를 Integer로 추론한다.
Integer result = GenericMethodExample.convert(123);
3. 컴파일러 경고 무시
@SuppressWarnings("unchecked") // 경고 무시
String result = GenericMethodExample.convert(123);
(3) 타입 바운드 - 제네릭 제한
제네릭 타입 매개변수에 대한 제한을 설정한다. extends 키워드를 사용하여 특정 클래스나 인터페이스의 하위 타입만 허용한다.
public <T extends Number> double sumOfList(List<T> list) {
double sum = 0.0;
for (T element : list) {
sum += element.doubleValue();
}
return sum;
}
(4) 언바운드 타입 - 와일드 카드 타입 <?>, <? extends ..>, <? super ..>
<?>와 같은 형태로 표현되며, 어떤 타입도 매치될 수 있는 와일드카드 타입이다. 제한 없이 모든 타입을 다룰 수 있다.
public void printList(List<?> list) {
for (Object obj : list) {
System.out.println(obj);
}
}
※ 바운드 와일드 타입 <? extends T>, <? super T>
:와일드카드의 상한 또는 하한을 제한하는 데 사용된다.
ex. <? extends Number>는 Number 또는 Number의 하위 클래스만 허용
ex. <? super Integer>는 Integer 또는 Integer의 상위 클래스만 허용
(4) 제네릭 서브타이핑
제네릭에서는 서브타이핑(subtyping) 규칙을 따른다. List<String>은 List<Object>의 서브타입이며, 제네릭 타입 간에도 상속 및 구현 관계가 유지된다.
(5) 제네릭 타입의 상속과 구현
부모 클래스의 타입 매개변수를 자식 클래스에서 유지하거나 변경할 수 있다. 아래 예제는 구현 관계를 보여준다.
// 제네릭 인터페이스 정의
public interface Container<T> {
void add(T item);
T get(int index);
}
// 구현 클래스
// 제네릭 클래스 정의
public class ListContainer<E> implements Container<E> {
// 제네릭 타입 E는 리스트의 요소 타입을 나타낸다.
private List<E> items = new ArrayList<>();
@Override
public void add(E item) {
items.add(item);
}
@Override
public E get(int index) {
return items.get(index);
}
}
public class Main {
public static void main(String[] args) {
// 제네릭 리스트 인스턴스 생성
ListContainer<String> stringList = new ListContainer<>();
// 리스트에 요소 추가
stringList.add("Java");
}
'JAVA' 카테고리의 다른 글
| > [Java] 마커 인터페이스(Marker Interface)에 대하여 (0) | 2024.05.07 |
|---|---|
| > [Java] 자바 프록시 구현 (JDK Dynamic Proxy, CGLIB) (0) | 2024.01.21 |
| > [Java] hashcode() & equals() 메서드 재정의와 관계성 (0) | 2023.07.18 |
| > [Java] 람다 (Lambda) 개념과 사용 (0) | 2022.11.11 |
| > [Java] 직렬화 (Serializable) 에 대해서 3. Java 객체 직렬화/역직렬화 방법(Json, YAML, binary ) - GSON, YAML Bean, Protocol Buffers 등 (0) | 2020.12.14 |