제네릭
제네릭은 jdk 1.5부터 도입되었습니다. 타입을 미리 지정하지 않고 컴파일할 때 체크할 수 있도록 하는 기능입니다. 미리 타입을 지정해 두면 타입을 형변환하거나 체크해야 하는 번거로움이 있습니다. 제네릭을 사용하면 이와 같은 부담에서 벗어날 수 있습니다. 제네릭은 클래스와 메서드에 선언할 수 있습니다. 어떻게 선언하고 사용하는지 살펴봅시다.
제네릭에서 사용하는 형식 매개변수 관례
E | 요소 |
K | 키 |
N | 숫자 |
T | 형식 |
V | 값 |
1. 제네릭 클래스
제네릭 클래스는 형식 매개변수를 사용하는 클래스입니다.
일반 클래스 Generic
class Generic (var property: Any?) { // 주 생성자
var variabe: Any? = null // 프로퍼티
constructor(property2: Any?, property3: Any?) : this(property2) {} // 부 생성자
fun setProperty(variable: Any?): Any? { // 메서드
variabe = variable
}
}
지금까지 봐왔던 일반적인 클래스입니다. 주 생성자, 프로퍼티, 부 생성자, 메서드 매개변수, 반환값의 타입이 Any?로 고정되어 있습니다.
제네릭 클래스로 변경한 클래스 Generic
class Generic<T> (var property: T) {
var variabe: T? = null
constructor(property2: T, property3: T) : this(property2) {}
fun setProperty(property: T) : T {
this.property = property
return property
}
}
Any? 였던 타입이 T로 변경되었습니다. T 자료형은 클래스 내의 주 생성자, 부 생성자에, 메서드에 사용할 수 있습니다.
프로퍼티는 기본적으로 초기화되어 있어야 하기에 생성자로 초기화되지 않는 프로퍼티의 자료형으로는 잘 사용하지 않습니다.
T 자료형은 클래스를 선언하는 시점이 아닌 인스턴스를 생성하는 시점에 지정됩니다.
제네릭 클래스의 인스턴스 생성 예시
<> 안에 지정하고 싶은 자료형을 입력합니다.
var generic = Generic<String>("hi generic")
다형성
이전에 공부했던 다형성을 기억하시나요? 객체 지향 프로그래밍의 특징 중 하위 클래스의 인스턴스가 상위 클래스의 참조변수로 선언될 수 있었던 특징입니다.
open class Vehicle {
...
}
open class Car : Vehicle() {
...
}
class Truck : Car() {
...
}
// Vehicle > Car > Truck
var vehicle : Vehicle = Vehicle() // 가능
var car : Car = Car() // 가능
var truck : Truck = Truck() // 가능
var carTruck : Car = Truck() // 가능
var vehicleTruck : Vehicle = Truck() // 가능
var truckCar : Truck = Car() // 불가능
제네릭에서는 이러한 다형성에 제한이 있습니다.
var car: Generic<Car> = Generic<Car>() // O
var car: Generic<Car> = Generic() // 으로 생략 가능
var car: Generic<Car> = Generic<Truck>() // X
우선, 대입된 타입이 상속관계일지라도 다형성이 성립되지 않습니다. 참조변수와 생성자에 대입된 타입이 일치해야 합니다.
open class Generic<T> {
...
}
class Generic2<T> : Generic<T>() {
...
}
var car: Generic<Int> = Generic2<Int>() // O
제네릭 클래스 자체가 상속관계에 있고 대입된 타입이 같은 것은 다형성이 성립됩니다.
2. 제네릭 메서드
메서드가 호출될 때 컴파일러가 자료형을 추론합니다. 반환 자료형과 매개변수 자료형에 사용할 수 있습니다.
fun<T> add(a: T, b: T, op: (T, T) -> T): T {
return op(a, b)
}
제네릭 함수의 호출 예시
<> 안에 지정하고 싶은 자료형을 입력합니다.
method<Int>(1, 2, {a, b -> a + b})
대입할 타입의 제한
: 를 통해 대입할 자료형을 특정 클래스를 상속받는 클래스 혹은 특정 인터페이스를 구현하는 클래스로 제한할 수 있습니다.
또한, null이 허용되지 않는 자료형을 입력해 null 가능 자료형의 입력을 제한할 수 있습니다.
다수의 조건으로 제한하려면 where 키워드를 사용합니다. where 키워드 뒤의 조건은 &로 묶입니다 따라서 모두 해당하는 경우로 제한됩니다.
class GenericNumber<T: Number> {}
fun main() {
val obj1 = GenericNumber<String>() // 오류
}
----------------------
class GenericNotNull<T: Any> {}
fun main() {
val obj2 = GenericNotNull<Int?>() // 오류
}
----------------------
interface InterfaceA
interface InterfaceB
class ImplA: InterfaceA, InterfaceB
class ImplB: InterfaceB
class GenericTwo<T> where T:InterfaceA, T:InterfaceB {}
fun main() {
val obj3 = GenericTwo<ImplA>()
val obj4 = GenericTwo<ImplB>() // 오류
}
이번 글에서는 코틀린의 제네릭에 대해 살펴봤습니다. 코틀린의 제네릭은 자바의 제네릭과 크게 다르지 않기 때문에 자바를 공부했던 분이시라면 쉽게 이해하셨으리라 생각합니다. 다음 글에서는 제네릭과 관련된 in & out 키워드를 사용하는 변성에 대해 설명드려보도록 하겠습니다. 읽어주셔서 감사합니다.
'개발언어 > Kotlin : 코틀린' 카테고리의 다른 글
코틀린 익히기 10 - 배열 (0) | 2023.10.01 |
---|---|
코틀린 익히기 9 - 변성 Variance (0) | 2023.09.30 |
코틀린 익히기 7 - 다양한 클래스와 인터페이스 (0) | 2023.09.27 |
코틀린 익히기 6 - 프로퍼티와 object (0) | 2023.09.25 |
코틀릭 익히기 5 - 클래스와 객체 (0) | 2023.09.21 |