Java 제네릭Java Generics자바 제네릭 메소드자바 제넥 T자바 제네릭 사용이유

Java 기초 제네릭(Generics)이란?

KUKJIN LEE
KUKJIN LEE
2024년 9월 11일
177

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);
    }
}

 

각 계층에서 제네릭을 적절히 활용하면 더 유연하고 확장 가능한 코드를 작성할 수 있습니다. 하지만 예시코드와 같이 제네릭의 사용은 코드의 복잡성을 증가시킵니다.

관련 글

[SQL 입문] 필요한 데이터만 콕 집어 가져올 수 있는 WHERE 절

사용자는 테이블에 있는 모든 데이터를 다 보고 싶어 하지는 않습니다. 오히려 특정 항목에 대한 데이터만 가져오고 싶을 때가 훨씬 많습니다. 예를 들어, 어떤 사용자의 이메일이나 ID는 알고 있는데, 그 사람이 언제 우리 앱에 가입했는지 확인하고 싶다고 가정해 봅시다...

2026년 1월 27일10

SQL에서 SELECT란 무엇인가?

데이터베이스를 거대한 서류 보관함이라고 생각하면 됩니다. 보관함 안에는 수많은 데이터가 차곡차곡 쌓입니다. SELECT는 이 보관함에서 "내가 원하는 정보를 찾아줘!"라고 요청하는 명령어입니다. 이 과정을 전문 용어로 '쿼리(Query)'라고 부릅니다. &amp;nbs...

2026년 1월 26일10

[SQL 기초] "언제 하나씩 다 넣어?" 데이터 한 번에 넣기

개발을 하다 보면 데이터베이스(DB)에 샘플 데이터를 대량으로 넣어야 할 때가 있습니다. 메뉴 100개를 추가해야 하는데 INSERT 문을 100번 쓰고 있다면? 너무 비효율적이죠! 오늘은 SQL에서 여러 데이터를 한 번에 넣는 '다중 삽입(Multiple Inse...

2026년 1월 21일13

SQL 데이터 삽입하기

데이터가 없는 데이터베이스는 연료 없는 로켓과 같습니다. 이제 'Missions' 테이블에 새로운 행(row)을 추가하는 방법을 알아보겠습니다. "삽입(Inserting)"은 '데이터 추가'를 의미합니다. 기존 Missions 테이블 ...

2026년 1월 20일12