+ 항공대학교 김철기 교수님의 객체 지향 프로그래밍 과목 내용를 정리한 글입니다.
제네릭과 파라미터
제네릭이란?
=> 클래스 내부에서 사용하는 특정 대상의 타입을 파라미터화 한 것
ex) ArrayList<T> : T 타입을 가지는 원소의 ArrayList
ex) HashMap<K, V> : K타입의 키와 V타입의 값을 가지는 Entry의 HashMap
+ T, K V 를 타입 파라미터라고 한다.
제네릭을 사용하여 참조 변수 및 객체를 정의할 수 있다.
- 타입 파라미터에 대한 실제 타입을 지정해야한다.
- 타입 추론이 가능할 경우 타입 인자를 생략할 수 있다.
< 제네릭 사용 예시 코드 1 >
fun main() {
val list = ArrayList<Int>()
list.add(5)
list.add(6)
// list.add("Hello")는 에러 발생
list.forEachIndexed { idx, v ->
println("$idx: $v")
}
val list2 = ArrayList<String>()
list2.add("Hello")
list2.add("World")
list2.forEachIndexed { idx, v ->
println("$idx: $v")
}
}
< 제네릭 사용 예시 코드 2 >
fun main() {
val map = HashMap<String, Int>()
map["I"] = 1
map["V"] = 5
map["X"] = 10
map["L"] = 50
map.forEach {k, v ->
println("$k -> $v")
}
}
< 타입 추론 예시 코드 >
fun main() {
val map: HashMap<String, Int> = HashMap()
val arr = arrayOf("abc", "def")
}
제네릭 정의
사용자가 제네릭을 정의할 수 있다.
class TreeNode<T>(val data: T) {
var parent: TreeNode<T>? = null
private set
// 자식들을 담을 리스트
private val _children = arrayListOf<TreeNode<T>>()
val children: List<TreeNode<T>> get() = _children
// 자식 문자열을 TreeNode 객체로 생성 후 _children 리스트에 추가 + 자식 객체의 부모를 자신으로 변경
fun addChild(data: T) = TreeNode<T>(data).also {
_children += it
it.parent = this
}
override fun toString() = _children.joinToString(prefix = "$data {", postfix = "}")
}
fun main() {
// <String>을 적지 않아도 타입 추론 가능
val root = TreeNode("Hello").apply {
addChild("World")
addChild("!!")
}
println(root) // Hello {World {}, !! {}}
}
제네릭의 상속
타입 파라미터를 특정하여 일반 클래스 형태로 상속이 가능하다.
타입 파라미터를 상위 타입의 타입 파라미터로 넘길 수 있다.
타입 파라미터를 가지는 함수도 만들 수 있다.
open class DataHolder<T>(val data:T)
class TreeNode<T>(data: T) : DataHolder<T>(data){
var parent: TreeNode<T>? = null
private set
// 자식들을 담을 리스트
private val _children = arrayListOf<TreeNode<T>>()
val children: List<TreeNode<T>> get() = _children
// 자식 문자열을 TreeNode 객체로 생성 후 _children 리스트에 추가 + 자식 객체의 부모를 자신으로 변경
fun addChild(data: T) = TreeNode<T>(data).also {
_children += it
it.parent = this
}
override fun toString() = _children.joinToString(prefix = "$data {", postfix = "}")
}
// 제네릭 확장 함수 선언
fun<T> TreeNode<T>.addChildren(vararg data: T) { // vararg: 같은 타입의 데이터 여러 개를 입력 받을 수 있다.
data.forEach {
addChild(it)
}
}
fun main() {
// <String>을 적지 않아도 타입 추론 가능
val root = TreeNode("Hello").apply {
addChildren("World", "!!")
}
println(root) // Hello {World {}, !! {}}
}
타입 파라미터의 바운드
타입 파라미터가 특정 class나 interface를 상속한 경우로만 제네릭을 한정하는 것도 가능하다.
open class DataHolder<T>(val data:T)
class TreeNode<T>(data: T) : DataHolder<T>(data){
var parent: TreeNode<T>? = null
private set
// 자식들을 담을 리스트
private val _children = arrayListOf<TreeNode<T>>()
val children: List<TreeNode<T>> get() = _children
// 자식 문자열을 TreeNode 객체로 생성 후 _children 리스트에 추가 + 자식 객체의 부모를 자신으로 변경
fun addChild(data: T) = TreeNode<T>(data).also {
_children += it
it.parent = this
}
override fun toString() = _children.joinToString(prefix = "$data {", postfix = "}")
}
// 제네릭 확장 함수 선언
fun<T> TreeNode<T>.addChildren(vararg data: T) { // vararg: 같은 타입의 데이터 여러 개를 입력 받을 수 있다.
data.forEach {
addChild(it)
}
}
fun<T: Number> TreeNode<T>.averageChildren(): Double {
var count = 0
var sum = 0.0
children.forEach{
sum += it.data.toDouble()
count++
}
return sum / count
}
fun main() {
val root = TreeNode(1).apply {
addChildren(2, 3)
}
println(root.averageChildren()) // 2.5
}
< 에러 발생 >
fun main() {
val root = TreeNode("Hello").apply {
addChildren("World", "!!")
}
println(root.averageChildren()) // Number를 상속하지 않는 String형은 불가하므로 에러 발생
}
'Study > Kotlin Study' 카테고리의 다른 글
[Kotlin] 파일과 I/O 스트림 (2) | 2023.11.27 |
---|---|
[Kotlin] 컬렉션 유틸리티 (1) | 2023.11.21 |
[Kotlin] 컬렉션 타입 (0) | 2023.11.20 |
[Kotlin] 봉인된 클래스 (1) | 2023.11.14 |
[Kotlin] 부호 없는 정수 (0) | 2023.11.14 |