KAKAO.GG
백엔드

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

65870717953200389e7820de
2024. 9. 11.
조회 104
#Java 제네릭, Java Generics, 자바 제네릭 메소드, 자바 제넥 T, 자바 제네릭 사용이유, 자바 제네릭 메소드 예제, JAVA 제네릭

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

 

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

Java 기초 제네릭(Generics)이란? - Tech Data - KAKAO.GG