기록

안드로이드 코딩하며 익히는 코틀린 문법 (생성자 문법, 제너릭) 본문

[Study]/Kotlin

안드로이드 코딩하며 익히는 코틀린 문법 (생성자 문법, 제너릭)

Dannnnnn 2022. 6. 15. 11:29
반응형

1. 생성자 문법

class SongListAdapter : ListAdapter<Song, SongListAdapter.ViewHolder>(SongDiffCallback()) {

	...

}

class SongDiffCallback : DiffUtil.ItemCallback<Song>() {
	...
}

SongListAdapter 클래스 안에 SongDiffCallback이라는 클래스가 ListAdapter 클래스의 생성자로 들어갔다.

그런데 SongListAdapter 코드 블럭 안에 SongDiffCallback가 쓰이지 않았다.

왜 들어간 것인가?

 

ListAdapter 클래스 안에서 쓰였기 때문!

이해를 돕는 예제 코드를 첨부한다.

 

fun main() {
    
    //val korean = Human(Korean())
    val korean = KoreanPerson()
    korean.meetNewHuman()
    korean.goKaraoke()
    
}

class KoreanPerson: Human(Korean()) {
    fun goKaraoke() = navigateTo("karaoke")
}


abstract class Human(val language: Language) {
    
    fun meetNewHuman() = print(language.sayHi());
}

class English: Language() {
    override fun sayHi() : String = "Hello!"
}

class Korean: Language() {
    override fun sayHi() : String = "안녕!"
}

abstract class Language {
    abstract fun sayHi(): String
}


fun navigateTo(location: String) {}

//val korean = Human(Korean()) 라인의 문제점은

1. abstract 클래스를 직접 객체 선언할 수 없을 뿐더러

2. goKaraoke 함수를 Human 클래스에 만들시 british Person을 생성하면 영국인이 이 함수를 쓸 수 있게 돼버린다.

 

그리고 메인 함수 아래에 class KoreanPerson: Human(Korean()) 라인을 보면

위 안드로이드 코드와 마찬가지로 KoranPerson 클래스가 상속받는 Human 클래스의 생성자로 Korean 클래스가 들어갔으나

KoreanPerson 클래스에서 Korean 클래스가 직접 쓰이진 않았다.

하지만 Human 클래스를 보면 Language 클래스 타입의 Korean 클래스가 매개변수로 들어가 버젓이 쓰이는 것을 볼 수 있다.

 

이처럼 코드는 반드시 유기적으로 동작한다. 생략이란 없다.

 

2. 제너릭

class SongListAdapter : ListAdapter<Song, SongListAdapter.ViewHolder>(SongDiffCallback()) {

	...

}

여기서 저 <Song, SongListAdapter.ViewHolder> 는 정확히 뭘 하는 녀석일까? 대충 제네릭이란 건 알겠는데...

 

이해를 돕는 예제 코드를 보자.

 

fun main() {
    val pen = Pen("Blue Pen")
    val penBox = PenBox()
    penBox.putPen(pen)
    print(penBox.currentPen()?.name)
}

class PenBox() {
    var innerPen: Pen? = null
    
    fun putPen(pen: Pen) {
        innerPen = pen
    }
    
    fun emptyBox() {
        innerPen = null
    }
    
    fun currentPen(): Pen? {
        return innerPen
    }
}

class Pen(val name: String)

이렇게 펜 하나를 담는 박스가 있다.

당장은 펜이 하나지만 만약 지우개, 룰러 등의 문구용품이 계속 생기는 상황이라면?

EraserBox, RulerBox들을 하나하나 만들어야 한다.

이 작업은 딱봐도 비효율적이다.

 

EraserBox를 새로 만들어서 PenBox와 비교해보자.

둘의 차이점은 결국 변수명을 제외하고 클래스 내부에 사용되는 자료형 타입 뿐이다.

이런 경우에 사용하는 것이 제너릭이다.

 

fun main() {
    val pen = Pen("bluePen")
    val eraser = Eraser("whiteEraser")
    val box = BigBox<Eraser, Pen>()
    box.putItem(eraser, pen)
    // box.putItem(pen, eraser)
    println(box.currentItem1()?.name)
    println(box.currentItem2()?.name)
    box.emptyBox()
    println(box.currentItem1()?.name)
    println(box.currentItem2()?.name)
    
    
    
}

class BigBox<TYPE1, TYPE2>() {
    var innerItem1: TYPE1? = null
    var innerItem2: TYPE2? = null
    
    fun putItem(item1: TYPE1, item2: TYPE2) {
        innerItem1 = item1
        innerItem2 = item2
    }
    
    fun emptyBox() {
        innerItem1 = null
        innerItem2 = null
    }
    
    fun currentItem1(): TYPE1? {
        return innerItem1
    }
    fun currentItem2(): TYPE2? {
        return innerItem2
    }
}


class Box<TYPE>() {
    var innerItem: TYPE? = null
    
    fun putItem(item: TYPE) {
        innerItem = item
    }
    
    fun emptyBox() {
        innerItem = null
    }
    
    fun currentItem(): TYPE? {
        return innerItem
    }
}

class Pen(val name: String) {}
class Eraser(val name: String) {}
class Ruler(val name: String) {}

class Box<TYPE>()에서 <TYPE>은 매개변수도 뭣도 아닌 그냥 이 클래스는 제너릭을 사용한다고 알려주는 것이다.

그리고 내부 라인을 보면 기존에 사용된 Pen 객체를 TYPE으로 대체한 것 뿐이다.

변수명은 변수명일 뿐이고 지우개, 룰러 등을 자연스럽게 담고자 변수의 pen을 item으로 변경했을 뿐이다.

 

class BigBox<TYPE1, TYPE2>() 은 그냥 클래스 안에 제너릭 타입이 두개 사용된다고 알려주는 것이다.

 

추가로, val box = BigBox<Eraser, Pen>() 라인에서 두 제너릭 타입의 순서를 정해줬다.

변수를 선언하며 꺽쇠 안에 타입을 기술하는 순간 하위 라인들의 TYPE1, TYP2이 각각 Eraser과 Pen으로 대체된다.

그렇기에 box.putItem(pen, eraser) 처럼 두 순서를 바꾸면 타입이 안맞아 오류가 발생한다.

 

반응형