티스토리 뷰

Generics란?
- Generics는 클래스, 인터페이스, 메서드에 사용할 타입을 파라미터화할 수 있도록 해주는 기능
- 주로 컴파일 타임에 타입 안정성을 보장하고, 불필요한 타입 캐스팅을 줄이는 데 사용
Generics의 주요 목적
- 타입 안정성
- 잘못된 타입 사용으로 인한 오류 방지
- 코드 재사용성
- 타입에 구애받지 않는 유연한 코드 작성
- 가독성 향상
- 명시적으로 타입을 정의하여 코드 이해도 증가
Generics의 기본 사용법
- Generics는 주로 <> (다이아몬드 연산자)로 표현
1. 클래스와 인터페이스
public class Box<T> {
private T item;
public void setItem(T item) {
this.item = item;
}
public T getItem() {
return item;
}
}
위 코드에서 T는 Type Parameter로, Box 클래스가 어떤 타입이든 받을 수 있도록 한다
2. 메서드
public static <T> void printArray(T[] array) {
for (T element : array) {
System.out.println(element);
}
}
Generics 메서드는 클래스에 정의된 타입 매개변수와 독립적으로 동작하며, 메서드 수준에서 타입을 파라미터화할 수 있다
3. 컬렉션과 함께 사용
List<String> names = new ArrayList<>();
names.add("Alice");
names.add("Bob");
String name = names.get(0); // 타입 캐스팅 불필요
Generics를 사용하면 List가 특정 타입(String)만 받을 수 있도록 제한
Generics의 주요 원칙
1. 제네릭 타입은 컴파일 타임에만 존재한다
- Generics는 Type Erasure(타입 소거)를 사용하여 컴파일 시 타입 정보를 제거하고, 런타임에는 일반 클래스 또는 인터페이스로 동작
- 예를 들어, List<String>와 List<Integer>는 런타임에 동일한 List로 간주됩니다.
List<String> strings = new ArrayList<>();
List<Integer> integers = new ArrayList<>();
System.out.println(strings.getClass() == integers.getClass()); // true
2. 제한된 타입 매개변수
Generics에서 특정 타입만 허용하고 싶을 때 bounded type parameter를 사용할 수 있습니다.
상위 타입 제한
public <T extends Number> void process(T number) {
System.out.println(number.doubleValue());
}
- T는 Number의 하위 클래스만 가능
다중 제한
public <T extends Number & Comparable<T>> void process(T value) {
System.out.println(value.compareTo(value));
}
- T는 Number를 상속
- Comparable 인터페이스를 구현해야 함
3. 와일드카드
와일드카드는 Generics의 유연성을 높여줌
와일드카드 사용 예시
public void printList(List<?> list) {
for (Object obj : list) {
System.out.println(obj);
}
}
- ?
- 모든 타입을 허용.
- ? extends T
- T와 그 하위 타입만 허용.
- ? super T
- T와 그 상위 타입만 허용.
실무에서 Generics 사용 사례
1. 데이터 매핑
- 데이터베이스에서 가져온 결과를 특정 객체로 매핑하는 DAO(Data Access Object) 계층에서 Generics를 사용할 수 있음
예시: 데이터 매핑
public interface GenericDao<T, ID> {
void save(T entity);
T findById(ID id);
List<T> findAll();
}
public class UserDao implements GenericDao<User, Long> {
private final Map<Long, User> database = new HashMap<>();
@Override
public void save(User entity) {
database.put(entity.getId(), entity);
}
@Override
public User findById(Long id) {
return database.get(id);
}
@Override
public List<User> findAll() {
return new ArrayList<>(database.values());
}
}
// 사용 예시
GenericDao<User, Long> userDao = new UserDao();
User user = new User(1L, "Alice");
userDao.save(user);
User retrievedUser = userDao.findById(1L);
2. 서비스 계층에서의 응답 처리
- API 설계 시 서비스 계층에서 공통 응답 구조를 정의할 때 Generics를 활용하면 효율적입니다.
예시: 공통 응답 구조
public class ApiResponse<T> {
private String status;
private T data;
public ApiResponse(String status, T data) {
this.status = status;
this.data = data;
}
// Getter and Setter
public String getStatus() {
return status;
}
public T getData() {
return data;
}
}
// 사용 예시
ApiResponse<String> successResponse = new ApiResponse<>("success", "Operation completed.");
ApiResponse<User> userResponse = new ApiResponse<>("success", new User(1L, "Alice"));
3. 유효성 검사 도구
- Generics를 활용해 다양한 객체의 유효성을 검사하는 공통 유틸리티를 작성할 수 있습니다.
예시: Validator 유틸리티
public interface Validator<T> {
boolean validate(T entity);
}
public class UserValidator implements Validator<User> {
@Override
public boolean validate(User user) {
return user.getName() != null && user.getId() != null;
}
}
// 사용 예시
Validator<User> validator = new UserValidator();
User user = new User(1L, "Alice");
boolean isValid = validator.validate(user);
Generics의 장단점
장점
- 컴파일 타임 타입 체크
- 잘못된 타입 사용을 컴파일 타임에 방지
- 타입 캐스팅 제거
- 명시적 타입 캐스팅 불필요
- 코드 재사용성
- 다양한 타입을 처리하는 코드 작성 가능
단점
- 타입 소거
- 런타임에는 Generics 타입 정보가 소거되어 리플렉션 사용 시 제한이 있음
- 원시 타입 호환성 문제
- Generics는 기본 타입(int, double 등)을 직접 처리하지 못하고 Wrapper Class를 사용해야 함
면접 대비 질문
더보기
1. 제네릭이란 무엇인가요?
답변: 제네릭(Generics)은 자바에서 클래스, 인터페이스, 메서드에 사용할 데이터 타입을 일반화하여 작성할 수 있도록 도와주는 기능입니다. 제네릭을 사용하면 컴파일 타임에 타입을 검사하고, 잘못된 타입이 사용되는 것을 방지할 수 있습니다.
2. 제네릭을 사용하는 이유는 무엇인가요?
답변:
- 타입 안정성 제공: 잘못된 데이터 타입이 사용되는 것을 방지합니다.
- 캐스팅 제거: 명시적인 타입 변환이 필요 없습니다.
- 코드 재사용성 증가: 여러 타입에 대해 동일한 코드 작성이 가능합니다.
3. 제네릭을 사용하는 기본적인 코드 예시는 무엇인가요?
답변:
a. 제네릭 없이 사용하는 경우:
import java.util.ArrayList;
public class NoGenericsExample {
public static void main(String[] args) {
ArrayList list = new ArrayList(); // 타입 지정 X
list.add("Hello");
list.add(123); // 잘못된 타입 추가 가능
String str = (String) list.get(1); // ClassCastException 발생 가능
}
}
b. 제네릭을 사용하는 경우:
import java.util.ArrayList;
public class GenericsExample {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>(); // String 타입으로 제한
list.add("Hello");
// list.add(123); // 컴파일 에러 발생
String str = list.get(0); // 타입 캐스팅 불필요
System.out.println(str);
}
}
4. 제네릭 클래스란 무엇인가요?
답변: 제네릭 클래스는 클래스 정의 시 타입 매개변수를 사용하는 클래스입니다. 다양한 데이터 타입에 대해 하나의 클래스를 사용할 수 있습니다.
예제 코드:
// 제네릭 클래스 정의
public 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");
System.out.println(stringBox.getItem()); // 출력: Hello
Box<Integer> intBox = new Box<>();
intBox.setItem(123);
System.out.println(intBox.getItem()); // 출력: 123
}
}
5. 제네릭 메서드란 무엇인가요?
답변: 제네릭 메서드는 메서드 선언부에 타입 매개변수를 선언하여, 메서드 호출 시 타입을 지정할 수 있게 합니다.
예제 코드:
public class GenericMethodExample {
// 제네릭 메서드 정의
public static <T> void printArray(T[] array) {
for (T element : array) {
System.out.println(element);
}
}
public static void main(String[] args) {
String[] stringArray = {"A", "B", "C"};
Integer[] intArray = {1, 2, 3};
printArray(stringArray); // T는 String으로 결정
printArray(intArray); // T는 Integer로 결정
}
}
6. 와일드카드란 무엇인가요?
답변: 와일드카드(?)는 제네릭에서 특정 타입이 아닌 어떤 타입이라도 허용하기 위해 사용됩니다.
주요 유형:
- <?> (Unbounded Wildcard): 모든 타입 허용.
- <? extends Type> (Upper Bounded Wildcard): 특정 타입과 그 하위 클래스만 허용.
- <? super Type> (Lower Bounded Wildcard): 특정 타입과 그 상위 클래스만 허용.
예제 코드:
import java.util.ArrayList;
import java.util.List;
public class WildcardExample {
// Unbounded Wildcard
public static void printList(List<?> list) {
for (Object obj : list) {
System.out.println(obj);
}
}
// Upper Bounded Wildcard
public static void printNumbers(List<? extends Number> list) {
for (Number num : list) {
System.out.println(num);
}
}
public static void main(String[] args) {
List<String> stringList = new ArrayList<>();
stringList.add("Hello");
printList(stringList); // 모든 타입 가능
List<Integer> intList = new ArrayList<>();
intList.add(123);
printNumbers(intList); // Number와 하위 클래스만 가능
}
}
7. 제네릭에서 T, E, K, V는 무엇인가요?
답변: 제네릭 타입 매개변수 이름은 관례에 따라 다음과 같이 사용됩니다:
- T (Type): 임의의 데이터 타입.
- E (Element): 컬렉션에서 요소를 나타낼 때 사용.
- K, V: 키(Key)와 값(Value)을 나타낼 때 사용 (예: Map).
- ?: 와일드카드 (특정 타입이 아닌 모든 타입).
예제 코드:
public 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("Key: " + pair.getKey() + ", Value: " + pair.getValue());
}
}
8. 제네릭의 장점과 제한 사항은 무엇인가요?
장점:
- 타입 안정성: 컴파일 단계에서 타입 검사로 오류 방지.
- 코드 재사용성: 다양한 타입을 처리하는 하나의 클래스/메서드 작성 가능.
- 가독성 향상: 코드 명확성과 유지보수성 증가.
제한 사항:
- 기본 타입 사용 불가: 제네릭은 참조 타입만 지원합니다. (예: int 대신 Integer 사용)
- 런타임에 타입 정보 소거(Type Erasure): 실행 시 타입 정보는 제거되어 특정 타입 확인 불가.
- 정적 컨텍스트에서 타입 매개변수 사용 불가: 정적 메서드나 변수에서는 제네릭 타입 사용 불가.
9. 초보자가 많이 하는 실수는 무엇인가요?
- 타입 매개변수와 일반 타입 혼동:대신:
- Box<Integer> box = new Box<>();
- Box<int> box = new Box<>(); // 잘못된 코드 (기본 타입 불가)
- 와일드카드 잘못 사용:
- List<?> list = new ArrayList<>(); list.add("Hello"); // 컴파일 에러 (쓰기 불가)
10. Type Erasure의 한계점은 무엇인가요?
- 타입 소거로 인해 런타임에 타입 정보가 사라져 리플렉션으로 구체적인 타입을 알 수 없습니다. 이를 해결하려면 Class<T>와 같은 방식을 사용해야 합니다.
11. 왜 Java는 공변성(covariance)와 반공변성(contravariance)을 지원하지 않나요?
- Java는 명확성과 안전성을 위해 배열에서는 공변성을 지원하지만, Generics에서는 런타임 오류를 방지하기 위해 불변성(invariance)을 유지합니다.
12. 와일드카드와 타입 파라미터의 차이는 무엇인가요?
- 와일드카드는 불특정 타입을 표현하며 읽기 전용으로 주로 사용됩니다. 반면 타입 파라미터는 더 구체적으로 제네릭 메서드나 클래스에서 특정 타입으로 제한됩니다.
13. Generics와 리플렉션을 함께 사용할 수 있나요?
- Generics는 타입 소거를 사용하므로 리플렉션에서 원시 타입만 확인할 수 있습니다. 이를 해결하려면 TypeToken 또는 ParameterizedType을 활용해야 합니다.
14. Generics는 성능에 영향을 미치나요?
- Generics는 컴파일 타임에 타입을 체크하고 런타임에는 타입 소거로 동작하기 때문에 성능에 큰 영향을 미치지 않습니다. 다만, 기본 타입 대신 Wrapper Class를 사용할 때 박싱/언박싱으로 성능 저하가 발생할 수 있습니다.
15. Java의 Collections Framework에서 Generics는 어떻게 활용되나요?
- Java의 List, Set, Map과 같은 컬렉션 인터페이스는 모두 Generics를 활용하여 타입 안정성을 제공합니다. 예를 들어, List<String>은 문자열만 저장할 수 있도록 보장합니다.
'Language > Java' 카테고리의 다른 글
| [JAVA] 자바의 주요 라이브러리 정리 (0) | 2025.01.15 |
|---|---|
| [JAVA] Reflection에 대하여 (0) | 2025.01.14 |
| [JAVA] 람다와 함수형 인터페이스 (2) | 2025.01.13 |
| [JAVA] 예외 처리에 대하여 (0) | 2025.01.10 |
| [JAVA] Iteration과 Stream API 그리고 정렬 (0) | 2025.01.09 |
공지사항
최근에 올라온 글
최근에 달린 댓글
- Total
- Today
- Yesterday
링크
TAG
- 스프링
- 백트래킹
- 탐색 알고리즘
- Spring
- 알고리즘
- 프리코스
- B+Tree
- MSA
- 운영체제
- HTTP
- Spring Boot
- 우테코
- devops
- CPU 스케줄링
- Java
- 그리디 알고리즘
- 자료구조
- i/o모델
- 우선순위 큐
- 데이터베이스
- k8
- db
- 우아한 테크코스
- 동적 프로그래밍
- TRIE
- 자바
- restful api
- 해시 테이블
- CS
- 분할 정복
| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | 6 | |
| 7 | 8 | 9 | 10 | 11 | 12 | 13 |
| 14 | 15 | 16 | 17 | 18 | 19 | 20 |
| 21 | 22 | 23 | 24 | 25 | 26 | 27 |
| 28 | 29 | 30 | 31 |
글 보관함