Java 기초 제네릭(Generics)이란?
KUKJIN LEE • 4주 전 작성
1. 제네릭(Generics)이란?
제네릭은 클래스, 인터페이스, 메서드에서 사용할 데이터 타입을 컴파일 시에 미리 지정하지 않고, 실제 사용 시점에 타입을 지정할 수 있게 하는 기능입니다. 이를 통해 코드 재사용성을 높이고, 컴파일 시 타입 안전성을 보장할 수 있습니다.
2. 제네릭 클래스와 메서드
제네릭 클래스
타입 매개변수를 정의하여 다양한 타입을 다룰 수 있습니다.
public class Box<T> {
private T item;
public void setItem(T item) {
this.item = item;
}
public T getItem() {
return item;
}
}
제네릭 클래스는 여러 타입을 유연하게 처리할 수 있는 데이터 구조나 기능이 필요할 때 사용됩니다. 예를 들어, List
, Map
, Set
과 같은 컬렉션 클래스들은 제네릭을 사용해 다양한 타입의 객체를 처리할 수 있습니다.
Box<String> stringBox = new Box<>();
stringBox.setItem("Hello");
System.out.println(stringBox.getItem());
Box<Integer> integerBox = new Box<>();
integerBox.setItem(123);
System.out.println(integerBox.getItem());
제네릭 메서드
메서드 수준에서 타입 매개변수를 정의합니다.
public static <T> void printArray(T[] array) {
for (T element : array) {
System.out.print(element + " ");
}
System.out.println();
}
클래스 전체가 아닌 특정 메서드에서만 제네릭 타입이 필요할 때 사용됩니다. 같은 기능을 하는 메서드가 여러 타입의 인자를 받아야 할 경우 유용합니다.
Integer[] intArray = {1, 2, 3};
String[] stringArray = {"Hello", "World"};
printArray(intArray);
printArray(stringArray);
3. 와일드카드와 경계 설정
-
상한 경계 (
<? extends T>
): T 또는 그 하위 타입만 허용 -
하한 경계 (
<? super T>
): T 또는 그 상위 타입만 허용 -
무제한 와일드카드 (
<?>
): 모든 타입 허용
List<? extends Number> list = new ArrayList<Integer>();
4. 제네릭의 제한사항
-
기본 타입(primitive type) 사용 불가 (
int
대신Integer
사용) -
정적(static) 멤버에 제네릭 타입 매개변수 사용 불가
-
타입 소거(Type Erasure)로 인해 런타임에 제네릭 타입 정보가 유지되지 않음
-
제네릭 타입의 배열 생성 불가 (
new T[]
형태 사용 불가)
5. 예시
DAO (Data Access Object)
DAO 계층에서 제네릭을 사용하면 여러 엔티티 타입에 대해 재사용 가능한 데이터 접근 메서드를 구현할 수 있습니다.
public interface GenericDAO<T, ID> {
T findById(ID id);
List<T> findAll();
void save(T entity);
void update(T entity);
void delete(ID id);
}
public class UserDAO implements GenericDAO<User, Long> {
@Override
public User findById(Long id) {
// 구현 내용
}
@Override
public List<User> findAll() {
// 구현 내용
}
@Override
public void save(User entity) {
// 구현 내용
}
@Override
public void update(User entity) {
// 구현 내용
}
@Override
public void delete(Long id) {
// 구현 내용
}
}
Service
서비스 계층에서 제네릭을 사용하면 다양한 비즈니스 엔티티에 대해 일관된 서비스 인터페이스를 제공할 수 있습니다.
public interface CRUDService<T, ID> {
T findById(ID id);
List<T> findAll();
T save(T entity);
T update(T entity);
void delete(ID id);
}
public class UserService implements CRUDService<User, Long> {
private final UserDAO userDAO;
public UserService(UserDAO userDAO) {
this.userDAO = userDAO;
}
@Override
public User findById(Long id) {
return userDAO.findById(id);
}
@Override
public List<User> findAll() {
return userDAO.findAll();
}
@Override
public User save(User entity) {
userDAO.save(entity);
return entity;
}
@Override
public User update(User entity) {
userDAO.update(entity);
return entity;
}
@Override
public void delete(Long id) {
userDAO.delete(id);
}
}
RestController
RestController에서 제네릭을 사용하면 다양한 타입의 요청과 응답을 처리하는 범용 컨트롤러를 구현할 수 있습니다.
@RestController
@RequestMapping("/api")
public class GenericController<T, ID> {
private final CRUDService<T, ID> service;
public GenericController(CRUDService<T, ID> service) {
this.service = service;
}
@GetMapping("/{id}")
public ResponseEntity<T> getById(@PathVariable ID id) {
T entity = service.findById(id);
return ResponseEntity.ok(entity);
}
@GetMapping
public ResponseEntity<List<T>> getAll() {
List<T> entities = service.findAll();
return ResponseEntity.ok(entities);
}
@PostMapping
public ResponseEntity<T> create(@RequestBody T entity) {
T savedEntity = service.save(entity);
return ResponseEntity.status(HttpStatus.CREATED).body(savedEntity);
}
@PutMapping("/{id}")
public ResponseEntity<T> update(@PathVariable ID id, @RequestBody T entity) {
T updatedEntity = service.update(entity);
return ResponseEntity.ok(updatedEntity);
}
@DeleteMapping("/{id}")
public ResponseEntity<Void> delete(@PathVariable ID id) {
service.delete(id);
return ResponseEntity.noContent().build();
}
}
// 사용 예시
@RestController
@RequestMapping("/api/users")
public class UserController extends GenericController<User, Long> {
public UserController(UserService userService) {
super(userService);
}
}
각 계층에서 제네릭을 적절히 활용하면 더 유연하고 확장 가능한 코드를 작성할 수 있습니다. 하지만 예시코드와 같이 제네릭의 사용은 코드의 복잡성을 증가시킵니다.