밤빵's 개발일지
[TIL]20240715 제네릭의 와일드카드 <?> 본문
java 4주차를 날려들은 덕에 나는 제네릭에 대해서 잘 모르고 지나왔다. 코드리뷰를 받으면서 제네릭 와일드카드를 썼는 데 그에 대한 질문에 나는 매니저님께 대답하지 못 했다. 사실 그냥 되라고 쓴거였는데 그런 이유를 말할 순 없으니까... 명확한 이유를 알고싶기도했고, 제네릭에 대해서도 알아둬야해서 오늘은 그 내용을 정리했다.
제네릭에서 다형성을 적용하면 더욱 유연하고 확장이 가능한 코드를 작성할 수 있고, 코드의 재사용성과 유지보수성을 극대화 할 수있다...? 제네릭은 java에서 데이터 타입의 안정성을 보장하고, 코드 재사용성을 높이기 위해 도입된 중요한 기능 중 하나다. 특히 와일드카드<?>는 제네릭 타입을 유연하게 사용할 수 있게 해준다. (그저 제네릭을 <T> 정도만 기억하고 있던 나 반성해..!)
▶ 제네릭<Generic> 이란?
제네릭은 자바에서 타입을 일반화 하는 방법으로. 클래스나 메서드에서 사용할 데이터 타입을 미리 지정하지 않고, 런타임 시에 필요에 따라 사용할 수 있도록 설계하는 방식이다. 제네릭을 사용하면 타입 안전성을 보장할 수 있어서 컴파일 시점에서 타입 체크를 통해 잘못된 타입 사용을 방지할 수 있다. 예를들어서 List<String> 은 문자열만을 담을 수 있는 리스트를 의미하고, 다른 타입의 데이터가 들어가면 컴파일 오류가 발생한다.
▶ 와일드카드<?>란?
제네릭에서 <?>는 와일드카드라고 불리고, "어떤 타입이든지 가능"하다는 의미를 가진다. 와일드 카드는 제네릭 클래스나 메서드를 호출할 때 구체적인 타입을 정하지 않고, 그 자리에 올 수 있는 모든 타입을 허용한다. 즉 제네릭 타입의 경계를 명확히 하지 않으며서도, 타입 안정성을 유지하고 유연하게 코드를 작성할 수 있게 해주는 역할을 한다.
▶ 와일드 카드의 종류
와일드 카드는 크게 세가지로 나뉜다.
1. 제네릭 불특정 와일드카드 <?> :
→ "아무 타입이나 상관없다"는 의미로, 제네릭 타입을 정하지 않고, 다양한 타입을 허용하고자 할 때 사용된다.
→ 예를들어 List<?>는 모든 타입의 요소를 담을 수 있는 리스트를 의미한다.
2. 상한 제한을 가진 와일드카드 <? extends T> :
→ 특정 타입 T의 하위 클래스만 허용하도록 제한한다. 예를 들어, List<? extends Number>는 Number 또는 그 하위 클래스(Integer, Double 등)의 요소만 허용한다.
→ 주로 읽기 전용으로 데이터를 처리할 때 사용하며, 데이터를 추가하거나 수정하는 작업에는 제한이 있다.
3. 하한 제한을 가진 와일드카드 <? super T> :
→ 특정 타입 T의 상위 클래스만 허용하도록 제한한다. 예를 들어, List<? super Integer>는 Integer의 상위 클래스(Number, Object 등)의 요소만 허용한다.
→ 주로 쓰기 전용으로 데이터를 추가하거나 수정할 때 유용하다.
▶ 제네릭의 다형성
제네릭의 다형성(Polymorphism)은 제네릭 타입을 활용해서 다양한 타입의 객체를 처리할 수 있는 유연한 메서드나 클래스를 작성할 수 있게 해준다. 코드의 재사용성과 유지보수성을 높일 수 있다.
제네릭 다형성의 주요 특징은 다음과 같다.
→ 유연한 타입 처리:
제네릭을 사용하면 하나의 메서드나 클래스가 여러 타입을 유연하게 처리할 수 있다. 예를 들어, List<Number>는 List<Integer>와 List<Double>을 모두 처리할 수 있지만, 반대는 성립하지 않는다. 이는 제네릭 타입에 대한 다형성을 제공한다.
→ 와일드카드와의 조합:
와일드카드는 제네릭 다형성의 유연성을 더욱 확대시킨다. 예를 들어, List<? extends Number>는 List<Integer>와 List<Double> 모두를 처리할 수 있어, 타입 제한을 완화하면서도 타입 안전성을 유지할 수 있다.
▶ 제네릭의 다형성을 적용할 수 있는 방법
제네릭 다형성을 효과적으로 적용하기 위해서는 다음과 같은 방법을 사용 할 수 있다.
1. 제네릭 메서드 사용 :
제네릭 메서드는 메서드 시그니처에 제네릭 타입을 선언하여 다양한 타입의 매개변수를 처리할 수 있다. 이를 통해 여러 타입의 객체를 처리할 수 있는 유연한 메서드를 설계할 수 있다.
public <T> void printList(List<T> list) {
for (T element : list) {
System.out.println(element);
}
}
2. 와일드카드와 함께 상한/하한 제한 설정 :
위에서 설명한 것 처럼 제네릭 와일드카드(<? extends T>, <? super T>)를 사용하여 메서드나 클래스가 처리할 수 있는 타입의 범위를 제한하면서도 다형성을 유지할 수 있다. 예를 들어, 상한 제한 와일드카드는 읽기 전용 데이터를 처리할 때, 하한 제한 와일드카드는 쓰기 전용 데이터를 처리할 때 유용하다.
3. 제네릭 인터페이스와 클래스 정의 :
제네릭 인터페이스나 클래스를 정의하여 여러 타입의 객체를 다룰 수 있는 추상화 계층을 만든다. 이를 통해 코드의 유연성과 재사용성을 높일 수 있다.
public interface Repository<T> {
void save(T entity);
T findById(Long id);
}
→ 다양한 엔티티를 처리할 수 있는 제네릭 레포지토리 인터페이스 : 코드의 재사용성이 높아진다.
▶ 와일드카드와 다형성 사용 예시
과제1 코드리뷰를 하면서 정리하기로 했던 내용이라, 과제1의 GlobalExceptionHandler를 예시로 가져왔다.
package com.sparta.memo.controller;
import com.sparta.memo.exception.MemoNotFoundException;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.context.request.WebRequest;
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(RuntimeException.class)
public ResponseEntity<?> handleRuntimeException(RuntimeException ex, WebRequest request) {
return new ResponseEntity<>(ex.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
}
@ExceptionHandler(IllegalArgumentException.class)
public ResponseEntity<?> handleIllegalArgumentException(IllegalArgumentException ex, WebRequest request) {
return new ResponseEntity<>(ex.getMessage(), HttpStatus.BAD_REQUEST);
}
@ExceptionHandler(MemoNotFoundException.class)
public ResponseEntity<?> handleMemoNotFoundException(MemoNotFoundException ex, WebRequest request) {
return new ResponseEntity<>(ex.getMessage(), HttpStatus.NOT_FOUND);
}
}
→ GlobalExceptionHandler 클래스를 통해서 예외를 전역적으로 처리하고 있다. 이 클래스는 다양한 예외를 처리하기 위해서 ResponseEntity<?> 제네릭 반환 타입을 사용했다.
→ ResponseEntity<?>는 와일드카드 <?>를 사용하여 어떤 타입의 객체든지 반환할 수 있도록 되어있다. 코드의 유연성을 높이지만, 클라이언트가 응답의 구체적인 타입을 예측하기 어렵게 만들 수도 있다.
→ 이 접근 방식은 다양한 예외에 대한 일관된 예외 처리를 제공하고, 예외 발생 시 클라이언트에게 적절한 HTTP 상태 코드와 메세지를 반환할 수 있도록 해준다.
▶ <?>와일드카드 사용 시 주의점
와일드카드는 제네릭의 유연성을 높여주지만 주의점이 있다.
1. 타입 안전성이 떨어질 수 있다 :
→ <?> 와일드카드를 사용하면 어떤 타입이든 받을 수 있기 때문에, 컴파일러가 타입을 완전히 검증하지 못할 수 있다. List<?>는 모든 타입의 리스트를 허용하지만, 추가할 수 있는 요소의 타입이 불명확하여 안전하지 않다.
→ 주로 읽기 전용으로 사용하는 것이 좋다.
2. 데이터 추가가 제한된다 :
→ List<?>와 같은 경우, 해당 리스트에 null 외의 데이터를 추가하는 것이 제한된다. 와일드카드가 구체적인 타입이 아니기 때문에, 컴파일러가 어떤 타입이 안전한지 알 수 없기 때문이다.
3. API의 명확성을 떨어뜨릴 수 있다 :
→ <?>를 사용하면 코드의 유연성은 높아지지만, API 문서화가 모호해질 수 있다. 예를 들어, ResponseEntity<?>와 같은 반환 타입을 사용하는 경우, 클라이언트는 응답으로 어떤 타입의 데이터를 받을지 명확히 알기 어렵다.
4. 와일드카드 사용 시 특정 작업의 제한이 있다 :
→ <? extends T>는 읽기 전용으로 사용하고, <? super T>는 쓰기 전용으로 사용해야 하는 등, 각 와일드카드의 사용 목적을 명확히 이해하고 사용해야 한다..!
5, 너무 많은 유연성으로 인한 코드의 복잡성이 증가할 수 있다 :
→ 와일드카드를 남용하면 코드가 복잡해지고 가독성이 떨어질 수 있다. 와일드카드가 많이 사용된 코드에서는 타입 추론이 어려워지고, 예기치 않은 오류가 발생할 가능성이 커진다.
▶ 정리
제네릭은 자바의 기능중 하나로 타입 안전성을 높이고 코드의 유연성을 제공하는 중요한 도구이다. 와일드카드는 제네릭 타입을 더욱 유연하게 처리할 수 있게 해주지만, 잘못 사용하면 코드의 안전성과 명확성을 해칠 수 있다.
'개발Article' 카테고리의 다른 글
[TIL]20240717 RESTful의 의미 (0) | 2024.07.17 |
---|---|
[TIL]20240716 단일 항목에 Lambda표현식을 쓴 이유? (0) | 2024.07.17 |
[WIL]20240714 (0) | 2024.07.14 |
[TIL]20240713 Entity에 @Setter어노테이션을 사용하지 않는 이유? (0) | 2024.07.14 |
[TIL]20240712 Entity (0) | 2024.07.12 |