제네릭
, 다른 언어를 이미 경험하고 왔다면 낯선 단어는 아니다. 뭐 대충 얘기하면 자료형
을 나중에 선언하는? 그런 느낌이다. 우선 확실하게 집고 넘어가보자.
그래서 제네릭이 뭐고 왜 쓰는거에요? 🤔
제네릭
은 클래스 내부에서 사용할 자료형을 나중에 인스턴스를 생성할 때 확정하는 방법이다. 제네릭이 나오게 된 배경은 자료형의 객체들을 다루는 메소드나 클래스에서 컴파일 시간에 자료형을 검사하여 적당한 자료형을 선택하기 위해서이다. 제네릭을 사용하면 객체의 자료형을 컴파일할 때 체크하기 때문에 객체 자료형의 안정성을 높이고 형 변환의 번거로움이 줄어든다.
제네릭은 앵글 브래킷 <>
사이에 형식 매개변수
를 넣어 선언하고 하나 이상의 형식 매개변수를 지정할 수 있다. 형식 매개변수는 자료형을 대표하는 용어로 T와 같은 특정 영문자를 사용하여 나중에 필요한 자료형으로 대체한다. 보통 컬렉션
에 많이 사용한다.
class Box<T> (t : T) {
var name = t
}
fun main() {
val box1 : Box<Int> = Box<Int>(1)
val box2 : Box<String = Box<String>("kodo")
// 타입 추론이 가능하므로 자료형 생략 가능
// val box1 = Box<Int>(1)
// val box2 = Box<String>("kodo")
println(box1.name)
println(box2.name)
}
Box 클래스를 제네릭을 사용해서 정의했다. 위 코드를 실행하면 아래와 같은 결과가 출력된다.
1
kodo
제네릭을 사용하면 객체를 생성할 때 자료형의 타입을 결정한다. 따라서 인자의 자료형을 고정할 수 없거나 예측할 수 없을 때 사용하면 실행시간에 자료형을 결정할 수 있게 되므로 유용하다.
제네릭
을 형식 매개변수로 받는 메소드를 제네릭 메소드라고 한다.
fun <T> genericFun(arg : T) : T? { }
fun <K , V> put(key : K, value : V) : Unit { }
아래 코드는 배열에서 특정 원소의 인덱스를 찾아내는 코드이다.
fun <T> find(a : Array<T>, Target : T) : Int {
for (i in a.indices) {
if (a[i] == Target) return i
}
return -1
}
fun main() {
val arr1 : Array<String> = arrayOf("사과", "바나나", "수박", "멜론")
println(find<String>(arr1, "사과"))
}
제네릭 클래스나 메소드가 받는 형식 매개변수를 특정한 자료형으로 제한할 수도 있다. 형식 매개변수 다음에 콜론(:)과 자료형을 기입하면 형식 매개변수의 자료형이 된다.
class Calc<T : Number> {
fun plus(arg1 : T, arg2 : T) : Double {
return arg1.toDouble() + arg2.toDouble()
}
}
fun main() {
val calc1 = Calc<Int>()
println(calc1.plus(10, 10))
val calc2 = Calc<Double>()
println(calc2.plus(5.0, 5.0))
val calc3 = Calc<String>() // 오류!