제네릭 타입 파라미터: 타입 인자를 받는 타입

유우선·2026년 2월 23일

Kotlin Study📚

목록 보기
29/32
  • 제네릭스를 사용해 타입 파라미터를 받는 타입을 정의할 수 있음
  • 제네릭 클래스에 구체적인 타입을 넘기면 타입을 인스턴스화 할 수 있음
val authors = listOf("Dmitry", "Svetlana")
  • 컴파일러는 자동으로 author의 타입이 List임을 추론함

  • 빈 리스트를 정의할 때는 타입 인자를 명시해줘야 함

val readers: MutableList<String> = mutableListOf() // 변수 타입 지정
val readers = mutableListOf<String>() // 함수 타입 지정

제네릭 타입과 함께 동작하는 함수와 프로퍼티

제네릭 함수 → 여러 타입에 대응 가능

  • 타입 파라미터를 받음
  • 호출 시 구체적인 타입을 인자로 넘겨야 함

컬렉션 함수 → 대부분 제네릭 함수

// slice함수 예시
fun <T> List<T>.slice(indices: IntRange) : List<T>
  • 의 의미 (순서대로)
    1. 타입 파라미터 선언
    2. 수신객체의 타입 파라미터
    3. 반환 객체의 타입 파라미터

컬렉션 호출 시 타입 인자를 명시적으로 지정할 수 있으나 대부분 컴파일러가 추론할 수 있음

fun main() {
		val letters = ('a'..'z').toList()
		println(letters.slice<Char>(0..2))
		// [a, b, c]
		println(letters.slice(10..13))
		// [k, l, m, n]
}

filter 함수

// 함수 시그니처
fun <T> List<T>.filter(predicate: (T) -> Boolean): List<T>
fun main() {
		val authors = listOf("Sveta", "Seb", "Roman", "Dima")
		val readers = mutableListOf("Seb", "Hadi")
		println(readers.filter { it !in authors })
		// [Hadi]
}
  • 람다 파라미터의 it 변수의 타입은 컴파일러에 의해 추론됨
    • filter의 수신 객체인 readers의 타입인 List로 부터 it의 타입이 String임을 추론

타입 파라미터를 사용할 수 있는 위치

  • 클래스나 인터페이스 안에 정의된 메서드
  • 최상위 함수
  • 확장 함수
    • 수신 객체, 파라미터 타입

제네릭 확장 프로퍼티

  • 제네릭 함수를 구현하는 구문으로 제네릭 확장 프로퍼티를 선언할 수 있음
// 모든 List 타입에 대한 확장 파라미터
val <T> List<T>.penultimate: T
		get() = this[size - 2]
		
fun main() {
		println(listOf(1, 2, 3, 4).penultimate) // 파라미터 타입이 Int로 추론됨
		//3
}

제네릭 클래스 선언

  • 자바와 마찬가지로 홑화살괄호를 클래스나 인터페이스 이름 뒤에 작성
    • 타입 파라미터를 다른 일반 타입처럼 사용할 수 있음
interface List<T> {
	operator fun get(index: Int): T
	/* ... */
} 

제네릭 클래스의 확장 클래스

  • 기반 타입의 제네릭 파라미터에 대해 타입 인자를 지정
    • 구체적인 타입을 넘기거나
    • 타입 파라미터로 받은 타입을 넘길 수 있음
// 구체적인 타입 지정
class StringList : List<T> {
		override fun get(index: Int): String = this[index]
		/* ... */
}

// 제네릭 타입 파라미터를 List의 타입 인자로 넘김
class ArrayList<T>: List<T> {
		override fun get(index: Int): T = this[index]
		/* ... */
}
  1. StringList
    • String 타입의 원소만 저장 → String을 기반 타입으로 사용
    • 하위 클래스에서 함수를 오버라이드 하거나 사용하려면 타입 인자에 String을 명시해야 함
      • ex) get 함수의 반환 타입으로 String을 명시함
  2. ArrayList
    • 자신만의 타입 파라미터를 정의하면서 기반 클래스의 타입 인자로 사용
      • ArrayList의 T와 List의 T는 같지 않음
  • 클래스가 자신을 타입 인자로 참조할 수 있음
// Comparable 인터페이스 구현 예제
interface Comparable<T> {
		fun compareTo(other: T): Int
}

class String : Comparable<String> {
		override fun compareTo(other: String): Int = TODO()
}

타입 파라미터 제약

  • 클래스나 함수에 사용할 수 있는 타입을 제한하는 기능
    • ex) 특정 함수에 대해 타입 파라미터로 숫자 타입 만을 허용하도록 정의할 수 있음

어떤 타입을 제네릭 타입의 상계(upper bound)로 정의

  • 제네릭 타입을 인스턴스화할 때 사용하는 타입 인자는 반드시 그 상계 타입이거나 그 상계 타입의 하위 타입이어야 함
// 상계 지정 방법
fun <T : Number> List<T>.sum(): T
  • 타입 파라미터 이름 뒤에 콜론( : )을 표시하고 그 뒤에 상계 타입 명시
fun main() {
		println(ListOf(1, 2, 3, 4).sum())
		// 10
}
  • Number → 코틀린 표 준 라이브러리의 숫자 타입의 최상위 클래스
  • 상계를 정하고 나면 T 타입의 값을 상계 타입으로 취급할 수 있음
fun <T:Number> oneHalf(value: T): Double {
		return value.toDouble() / 2.0
}

fun main() {
		println(oneHalf(3))
		// 1.5
}

두 값 비교 예제

fun <T: Comparable<T>> max(first: T, second: T): T {
		return if (first > second) first else second
}

fun main() {
		println(max("kotlin", "java"))
		//kotlin
}
  • 비교할 수 없는 값을 입력하면 커파일 오류 발생
println(max("kotlin", 42))
// ERROR: Type parameter bound for T is not satisfied:
// 		inferred type Any is not a subtype of Comparable<Any>

타입 파라미터에 둘 이상의 제약 선언

fun <T> ensureTrailingPeriod(seq: T) where T : CharSequence, T : Appendable { // 타입 파라미터 제약 목록
		if(!seq.endWith('.')) { // CharSequence 인터페이스의 확장 함수 호출
				seq.append(.) // Appendable 인터페이스에 정의된 메서드 호출
		}
}

fun main() {
		val helloWorld = StringBuilder("Hello World")
		ensureTrailingPeriod(helloWorld)
		println(helloWorld)
		//Hello World.
}
  • StringBuilder → CharSequence와 Appendable을 모두 사용하는 클래스

널이 될 수 없는 타입 제한하기

  • 상계를 정하지 않은 타입 파라미터 → Any? 타입으로 치환
  • 널 가능성을 제한하려면 명시적으로 타입 상계를 지정해줘야 함
class Processor<T: Any> {
		fun process(value: T) {
				value.hashCode()
		}
}
  • <T: Any> → T 타입이 항상 널이 될 수 없는 타입이 되도록 보장
  • Any 타입 외에도 다른 널이 될 수 없는 타입을 사용해 상계를 정할 수 있음

자바와의 상호 운용

  • 자바에서 어노테이션 @NotNull을 사용해 널값을 차단한 경우
    • <t : T & Any>와 같은 구문으로 절대로 널이 될 수 없는 값으로 표시할 수 있음

0개의 댓글