IMCAT.DEV
로그인 가입
Kotlin 중급 자료구조

Kotlin 제네릭과 타입 시스템

Kotlin 제네릭과 타입 시스템 — 중급

제네릭과 타입 시스템

1. 도입 (왜 배워야 하는가)

Kotlin의 제네릭(Generic)과 타입 시스템은 프로그래밍에서 데이터의 유형을 안전하게 다룰 수 있게 해주는 강력한 도구입니다. 실무에서는 다양한 데이터 형식을 처리할 때, 코드의 재사용성과 타입 안정성을 높이기 위해 제네릭을 사용합니다. 예를 들어, 쇼핑몰의 장바구니 시스템을 구현할 때 다양한 상품을 담을 수 있도록 하려면 제네릭이 필수적입니다.

제네릭을 모르면 타입 안전성을 보장받지 못해 런타임 오류가 발생할 수 있으며, 이는 프로그램의 신뢰성을 떨어뜨립니다. 예를 들어, 정수형 리스트에 문자열을 추가하려고 시도하는 경우 프로그램은 예기치 않게 종료될 수 있습니다. 따라서 제네릭은 안전하고 유연한 코드 작성을 위해 반드시 이해해야 할 개념입니다.

2. 핵심 개념 설명

제네릭은 클래스, 인터페이스 및 함수에서 사용할 수 있는 타입 매개변수를 정의하는 방법입니다. 이를 통해 특정 데이터 타입에 종속되지 않고 다양한 타입에 대해 일반화된 코드를 작성할 수 있습니다.

  • 제네릭(Generic): 다양한 데이터 타입을 처리할 수 있도록 하는 프로그래밍 기법입니다. 마치 여러 브랜드의 물건을 담을 수 있는 다용도 가방과 같습니다.
  • 타입(Type): 변수나 값의 데이터 형식을 뜻합니다. 예를 들어, 숫자, 문자열 등이 있습니다.
  • 타입 매개변수(Type Parameter): 클래스나 함수에서 제네릭을 사용할 때 지정하는 매개변수입니다.
  • 용어설명
    제네릭다양한 타입을 처리할 수 있는 프로그래밍 기법
    타입 매개변수제네릭에서 사용하는 변수 형태의 타입

    3. 기본 코드 예제

    아래는 제네릭을 사용하는 간단한 리스트 클래스를 만들어보는 예제입니다.

    class Box<T>(val item: T) {
        fun getItem(): T {
            return item
        }
    }
    
    fun main() {
        val intBox = Box(123)         // Int 타입의 Box 생성
        val stringBox = Box("Hello")  // String 타입의 Box 생성
    
        println(intBox.getItem())     // 123 출력
        println(stringBox.getItem())   // Hello 출력
    }
  • class Box<T>(val item: T): 제네릭 클래스 Box를 정의하며, T는 타입 매개변수입니다.
  • fun getItem(): T: getItem 메서드는 T 타입의 아이템을 반환합니다.
  • val intBox = Box(123): 정수 타입 Box를 생성합니다.
  • println(intBox.getItem()): 123을 출력합니다.
  • val stringBox = Box("Hello"): 문자열 타입 Box를 생성합니다.
  • println(stringBox.getItem()): "Hello"를 출력합니다.
  • 4. 실전 활용 예제

    1. 장바구니 구현:
    2. 쇼핑몰에서 다양한 상품을 담을 수 있는 장바구니 클래스를 제네릭으로 구현할 수 있습니다.

      class Cart<T> {
          private val items = mutableListOf<T>()
      
          fun addItem(item: T) {
              items.add(item)
          }
      
          fun getItems(): List<T> {
              return items
          }
      }
      
      fun main() {
          val cart = Cart<String>() // 문자열 타입의 장바구니 생성
          cart.addItem("사과")
          cart.addItem("바나나")
          println(cart.getItems()) // [사과, 바나나] 출력
      }
    3. 데이터베이스 레포지토리:
    4. 다양한 타입의 데이터를 저장하고 검색할 수 있는 데이터베이스 레포지토리를 구현할 수 있습니다.

      class Repository<T> {
          private val data = mutableListOf<T>()
      
          fun save(item: T) {
              data.add(item)
          }
      
          fun findAll(): List<T> {
              return data
          }
      }
      
      fun main() {
          val intRepo = Repository<Int>()
          intRepo.save(1)
          intRepo.save(2)
          println(intRepo.findAll()) // [1, 2] 출력
      }

      5. 흔한 실수와 주의사항

    5. 타입 불일치:
    6.    val box = Box("Hello")  // String 타입
         // box.item = 123      // 오류 발생: Int를 String으로 할 수 없음

      - 올바른 코드:

         val box: Box<Any> = Box("Hello") // Any로 선언하면 모든 타입 가능
    7. 타입 매개변수의 사용을 잊음:
    8.    class Box {                     // 잘못된 코드
             fun getItem(): T { ... }   // T가 정의되지 않음
         }

      - 올바른 코드:

         class Box<T> {                 // T로 제네릭 클래스 정의
             fun getItem(): T { ... }
         }

      6. 정리

    9. 제네릭은 다양한 데이터 타입을 처리할 수 있도록 해주는 유용한 프로그래밍 기법이다.
    10. 타입 안전성을 보장함으로써 런타임 오류를 방지할 수 있다.
    11. 제네릭을 통해 코드의 재사용성을 높이고, 유지보수가 용이한 구조를 만들 수 있다.
    12. 다음에 학습하면 좋은 주제는 "고급 제네릭 사용법"이나 "타입 별칭(Type Alias)"입니다.