Kotlin

개요

Kotlin언어란 IntelliJ IDEA 소프트웨어를 제작한 JetBrain사가 2011년 제작한 프로그래밍 언어 [https://try.kotlinlang.org] (https://try.kotlinlang.org)

특징

JVM에서 동작하는 Kotlin언어는 Java언어와 100% 호환이 가능 Java언어로 작성된 스크립트를 Kotlin언어로 바꾸는 기능

Nullable

    fun strlen (s: String?) = s.length()

    Nullable

    fun strlen (s: String?) : Int =
        if (s != null) s.length() else 0

함수와 변수

  • fun 함수
    fun sum(a: Int, b: Int) : Int {
        return a+b
    }

    fun sum(a: Int, b: Int) : Int = a+b
  • 타입 추론 (Type Inference)
    fun sum(a: Int, b: Int) = a+b
  • 디폴트 값
    fun sum(a: Int, b: Int = 10) = a+b

    fun main(args: Array<String>){
        println(sum(1))
    }

    $ 11
  • var, val 변수 선언
    변경 가능한 변수 var
    변경 불가능한 변수 val
    var name: String
    var age: Int = 20
  • 변수 타입 추론
    var name = "Mongue"

클래스

  • class
    class Button {
        var id: Int = 0
    }

  • 객체 생성시 new 사용하지 않음
    val button = Button()
  • 가시성 변경자 (Visibility Modifier)
    public:     클래스 (모든곳),            최상위함수/변수 (모든곳)
    internal:   클래스 (같은 모듈내),       최상위함수/변수 (같은 모듈내)
    protected:  클래스 (하위 클래스 내),    최상위함수/변수 (사용 불가)
    private:    클래스 (같은 클래스 내),    최상위함수/변수 (같은 파일내)

클래스의 생성자 (Constructor)

  • 주생성자 (Primary Constructor)
    // 주 생성자 생략
    class Button {
        var id: Int =0
    }

    // 매개변수가 없는 주 생성자 생략
    class Button(){
        var id: Int =0
    }

    // 매개변수가 있고 클래스에 있는 프로퍼티를 초기화하기 위한 목적
    class Button(_id: Int){
        var id: Int = _id  //var id = _id //Int 생략 가능
    }

    // 클래스에 있는 프로퍼티를 매개변수로 정의할수 있음
    class Button(var _id: Int){
    }


    //예제
    class Button(var id: Int, val x: Int = 0, val y:Int = 0)

    fun main(args: Array<String>){
        val button1 = Button(100)
    }

    println("button1: ${button1.id} (${button.x} ${button.y})")
  • 부생성자 (Secondary Constructor) 부생성자는 클래스명 대신 Constructor라는 키워드를 사용해 정의한다.

    class Button {
        var id: Int = 0
        contructor(id: Int){
            this.id = id
        }
    }

    // 부생성자에서 주생성자를 반드시 재호출 :this
    class Button(var id: Int) {
        var text: String = ""
        contructor(id:Int, text:String): this(id){
            this.text = text
        }
    }

    fun main(args: Array<String>){
        val Button1 = Button(100)
        val Button2 = Button(101, "button2")
    }


    //주생성자로 대체 가능
    class Button(var id:Int, var text:String="", var isCheckbox:Boolean=false)

클래스의 초기화 블록 (Initializer Block)

  • 초기화 블록은 주생성자 호출 직후 실행되며, 부생성자보다 먼저 실행된다.
    class Button(var id:Int){
        var text:String=""
        init {
            println("Initializer Block:$id, $text")
        }
        constructor(id:Int, test:String): this(id) {
            this.text = text
            println("constructor(id, text) : ${this.text}")
        }
    }

class Button{
    var id: Int =0
    var text:String=""
    init {
        println("Initializer Block 1:$id, $text")
    }
    constructor(id:Int){
        this.id = id
        println("constructor(id) : ${this.id}")
    }
    init {
        println("Initializer Block 2:$id, $text")
    }
    constructor(id:Int, text:String): this(id) {
        this.text = text
        println("constructor(id, text) : ${this.id}, ${this.text}")
    }
    init {
        println("Initializer Block 3:$id, $text")
    }
}

fun main(args: Array<String>) {
    println("Hello, world!")
    val Button = Button(100, "Button")
}

$ Hello, world!
$ Initializer Block 1:0,
$ Initializer Block 2:0,
$ Initializer Block 3:0,
$ constructor(id) :
$ constructor(id, text) :



class Button(var id:Int){
    var text:String=""
    init {
        println("Initializer Block 1:$id, $text")
    }
    init {
        println("Initializer Block 2:$id, $text")
    }
    constructor(id:Int, text:String): this(id) {
        this.text = text
        println("constructor(id, text) : ${this.id}, ${this.text}")
    }
    init {
        println("Initializer Block 3:$id, $text")
    }
}

fun main(args: Array<String>) {
    println("Hello, world!")
    val Button = Button(100, "Button")
}

$ Hello, world!
$ Initializer Block 1:100,
$ Initializer Block 2:100,
$ Initializer Block 3:100,
$ constructor(id, text) : 100, Button

클래스 상속

    final:      오버라이트 불가,                    클래스 멤버의 기본 변경자
    open:       오버라이트 가능,                    반드시 open을 명시해야 가능
    abstract:   반드시 오버라이트 해야 함,          추상 클래스의 멤버에만 붙일수 있다. 추상 멤버에는 구현이 있을 수 없다.
    override:   상위 멤버를 오버라이딩 하는 중,     오버라이드 하는 멤버는 기본적으로 open 상태. 항위 클래스에서 오버라이드를 금지상태면 final을 명시해야 함.

  • 생성자와 오버라이드
    open class Book(val title:String, var price:Int){
        open fun printInfo(){
            println("Title: $title, Price:$price")
        }
    }

    class EBook(title:String, price:Int, var url:String): Book(title, price){
        override fun printInfo(){
            println("Title:$title, Price:$price, URL:$url")
        }
    }

    fun main(args:Array<String>){
        val book = Book("bbb",200)
        val ebook = EBook("cccc", 1000, "aaaaa")
        book.printInfo()
        ebook.printInfo()
    }

$ Title: bbb, Price:200
$ Title:cccc, Price:1000, URL:aaaaa
  • 오버라이드 금지 final
    open class EBook(title:String, price:Int, var url:String): Book(title, price){
        final override printInfo(){
            println("Title:$title, Price:$price, URL:$url")
        }
    }

  • 상속 클래스의 생성자를 사용 super
    open class Book(val title:String, var price:Int) {
    }
    class EBook: Book {
        var url = ""
        constructor(title:String, price:Int, url:String): super(title, price){
        }
    }

인터페이스 선언

  • interface
    interface Clickable {
        fun click()
    }

    class Button:Clickable{
        override fun click() = println("click")
    }
    interface Clickable {
        fun click()
        fun showOff() = println("show off")
    }

    class Button:Clickable{
        override fun click() = println("click")
    }

    fun main(args: Array<String>) {
        val button = Button()
        button.click()
        button.showOff()
    }

$ click
$ show off
  • 다중 인터페이스 상속
    interface Clickable {
        fun click()
        fun showOff() = println("click off")
    }
    interface Focusable {
        fun setFocus(b:Boolean) = println("I ${if (b) "got" else "lost"} focus.")
        fun showOff() = println("focus off")
    }

    class Button:Clieckable, Focusable{
        override fun click() = println("clicked.")
        override fun showOff() {
            super<Clickable>.showOff()
            super<Focusable>.showOff()
        }
    }

    fun main(args: Array<String>) {
        val button: Button()
        button.click()
        button.setFocus(true)
        button.showOff()
    }

$ clicked.
$ I got focus.
$ click off
$ focus off
  • 인터페이스 클래스의 프로퍼티
    interface User {
        val nickname: String
    }

    class PrivateUser(override val nickname:String): User
    class SubscribingUser(val email:String):User{
        override val nickname:String=getID()
        fun getID()=email.substringBefore('@')
    }

    fun main(args: Array<String>){
        println(PrivateUser("aaa").nickname)
        println(SubscribingUser("bbbb@cccc").nickname)
    }

$ aaa
$ bbbb

프로퍼티 (Property) getter, setter

  • 멤버 변수의 필드와 게터, 세터를 묶어 프로퍼티라고 한다. 값을 저장할수 없는 필드(Backing Field)
  • 멤버 변수 선언시, 바로 뒤에 각 멤버에 대한 게터와 세터가 구현된다. 사용자가 직접 구현할때도 바로 뒤에 붙여서 구현해야 한다.
    class Rectangle {
        var height:Int=0
        get()=field
        set(value){
            field=value
        }

        var width:Int=0
        get()=field
        set(value){
            field=value
        }
    }

    fun main(args:Array<String>){
        val rect = Rectangle()
        rect.height=10
        rect.width=20
        println("height:${rect.height}, width:${rect.width}")
    }

$ height:10, width:20
  • 커스텀 접근자
    class Rectangle(var height:Int, var width:Int) {
        val isSquare:Boolean
        get() = height==width
    }


    fun main(args:Array<String>){
        val rect = Rectangle(10,10)
        println("height:${rect.height}, width:${rect.width}, isSquare:${rect.isSquare}")
        rect.height=20
        println("height:${rect.height}, width:${rect.width}, isSquare:${rect.isSquare}")
    }

$ height:10, width:10, isSquare:true
$ height:20, width:10, isSquare:false
  • 접근자의 가시성 변경자 counter 프로퍼티는 var 타입이지만, set 앞에 private 접근자를 붙여 변경할수 없도록 함 게터로 접근은 가능하지만, 세터로 값을 변경할 수 없음
    class LengthCounter {
        var counter:Int=0
        private set

        fun addWord(word:String){
            counter+=word.length
        }
    }

    fun main(args: Array<String>){
        val lengthCounter = LengthCounter()
        //LengthCounter.counter = 1
        lengthCounter.addWord("aaa")
        println(lengthCounter.counter)
    }

$ 3

데이터 클래스

  • 프로퍼티만 갖는 클래스 ‘data’ 키워드 사용
    data class Client(val name:String, val postalCode:Int)
  • toString()
    data Client(val name:String, val postalCode:Int) {
        override fun toString() = "Client(name= $name, postalCode= $postalCode)"
    }



    data class Client(val name:String, val postalCode:Int)

    fun main(args:Array<String>) {
        val client = Client("aaa", 1234)
        println(client)
    }

$ Client(name=aaa, postalCode=1234)

  • equals()
    class Client(val name:String, val postalCode:Int) {
        override fun equals(other: Any?) : Boolean {
            if (other == null || other !is Client) return false
            return name == other.name && postalCode == other.postalCode
        }
    }



    data class Client(val name:String, val postalCode:Int)

    fun main(args:Array<String>) {
        val client1 = Client("aaa", 1234)
        val client2 = Client("aaa", 1234)
        println(client1 == client2)
        println(client1.equals(client2))
    }

$ true
$ true

  • hashCode()
    class Client(val name:String, val postalCode:Int) {
        override fun equals(other: Any?) : Boolean {
            if (other == null || other !is Client) return false
            return name == other.name && postalCode == other.postalCode
        }

        override fun hashCode(): Int = name.hashCode() * 31 + postalCode
    }

    data class Client(val name:String, val postalCode:Int)

    fun main(args:Array<String>) {
        val clientset = hashSetOf(Client("aaa", 1234))
        println(clientset.contains(Client("aaa", 1234)))
    }


$ true
  • copy()
    data class Client(val name:String, val postalCode:Int)

    fun main(args:Array<String>) {
        val client1 = Client("aaa", 1234)
        val client2 = client1.copy(postalCode = 1236)
        println(client1)
        println(client2)
    }

$ Client(name=aaa, postalCode=1234)
$ Client(name=aaa, postalCode=1236)

클래스의 확장

  • 확장 함수(Extension Function) : 코틀린으로 변환할수 없는 자바코드를 처리해야 할 경우
  • 확장 함수로 오버라이드할 수 없다.
  • 확장 프로퍼티 : 반드시 커스텀 게터 get() 를 정의해 줘야 한다.
    class Calculator {
        fun sum(a: Int, b: Int) = a+b
        fun minus(a: Int, b: Int) = a-b
    }

    fun Calculator.sum(a: Int, b: Int, c: Int) = sum(a,b)+c
    fun Calculator.minus(a: Int) = -a

    fun main(arga: Array<String>) {
        val calc = Calculator()
        println("1+2+3 = ${calc.sum(1,2,3)}")
        println("1 = ${calc.minus(1)}")
    }

$ 1+2+3 = 6
$ 1 = -1

Null

  • Non-Null 타입 : 컴파일시 에러 발생, NullPointerException 이 발생하지 않는다.
  • Nullable 타입 : ? 문자를 사용하여 Null 값을 가질수 있도록 허용, Null Check 없이 바로 접근 불가능
    class Bitmap(val width:Int, val height:Int) {
        val size: Int
        get() = width*height
        val map = ByteArray(size)
    }

    fun CreateBitmap(width:Int, height:Int):Bitmap? {
        if (width > 0 && height > 0) return Bitmap(width,height)
        else return null
    }

    fun main(args:Array<String>) {
        val bitmap : Bitmap? = CreateBitmap(10,10)
        if (bitmap != null) println(bitmap.size)
    }

$ 100

Null Check

  • Safe Calls : “?.” 왼쪽의 매개변수가 null이라면 null을 반환하며 구문을 종료하고, 아니라면 해당 변수를 Non-Null 타입으로 변환하여 프로퍼티에 접근한다.

    fun main(args:Array<String>) {
        val bitmap : Bitmap? = CreateBitmap(10,10)
        if (bitmap != null) println(bitmap.size)
    }

    fun main(args:Array<String>) {
        val bitmap : Bitmap? = CreateBitmap(10,10)
        println(bitmap?.size)
    }

    fun Person.cityName(): String {
        val city = this.company?.addr?.city
        return if (city != null) city else "Unkown"
    }
  • Elvis(엘비스) 연산바:”?:” 시계 방향으로 90도 회전하면 엘비스 프레슬리의 헤어스타일을 닮았다? 왼쪽에 있는 값이 null이 결우 오른쪽에 있는 값을 대입한다.
    fun Person.cityName(): String {
        val city = this.company?.addr?.city
        return city?:"Unkown"
    }

    fun Person.cityName(): String = this.company?.addr?.city?:"Unkown"
  • !! Null 이 아님

    fun main(args:Array<String>) {
        val bitmap : Bitmap? = CreateBitmap(10,10)
        bitmap : Bitmap? = CreateBitmap(10,10)
        println(bitmap!!.size)
    }

  • let 함수 let 함수를 사용해 Nullable 타입변수를 Non-Null 타입에 대입할 수 있다.
    class Bitmap(val width:Int, val height:Int) {
        val size: Int
        get() = width*height
        val map = ByteArray(size)
    }

    fun CreateBitmap(width:Int, height:Int):Bitmap? {
        if (width > 0 && height > 0) return Bitmap(width,height)
        else return null
    }

    fun DrawBitmap(bitmap: Bitmap){
        println("DrawBitmap")
    }

    fun main(args:Array<String>) {
        val bitmap : Bitmap? = CreateBitmap(10,10)
        bitmap?.let {
            DrawBitmap(it)
        }
    }

안전한 호출 연산자 ?.를 사용해서 'bitmap' 객체가 null인지 확인한후 null 이 아닐 경우에는 Non-Null 타입의 객체가 되어 let 함수를 실행한다. let 함수는 호출하는 객체를 인자로 넘기는 함수이므로 Non-Null 타입으로 변환된 bitmap 변수가 인자로 넘어와 DrawBitmap() 함수에 사용될 수 있다.
it 연산자는 매개 변수가 하나 뿐일 경우 매개 변수 이름 대신 사용할 수 있는 키워드

제어 함수

  • if
    fun GetMax(a:Int, b:Int): Int = if (a<b) b else a

    fun GetMax(a:Int, b:Int): Int {
        return if (a<b) {
            println("b bigger than a")
            b
        } else {
            println("a bigger than b")
            a
        }
    }

  • when
    fun printValue(a:Int) {
        when(a) {
            1 -> println("value:1")
            2 -> println("value:2")
            else -> println("value is neigher 1 nor 2")
        }
    }

    fun printValue(a:Int, str:String) {
        when(a) {
            str.toInt() -> println("str is a")
            else -> prinln("str is not a")
        }
    }

    fun printValue(a:Int, str:String) {
        when(a) {
            in 1..10 -> println("str is (1~10)")
            in 10..20 -> println("str is (10~20)")
            else -> prinln("str is not a")
        }
    }

    fun checkValue(a:Int) {
        when {
            a.isOdd() -> println("odd")
            a.isEven() -> println("even")
            else -> println("what")
        }
    }
  • 스마트 캐스트 (Smart Cast) is 또는 !is 연산자를 통해 타입을 검사하여 객체 타입이 일치할 경우 객체가 해당 타입으로 자동 캐스팅 되는 기능

  • while, do-while

  • in

    val oneToTen = 1..10

    fun isLetter(c: Char) = c in 'a'..'z'||c in 'A'..'Z'
    fun isNotDigit(c: Char) = c !in '0'..'9'

    fun recognize(c:Char) = when (c) {
        in '0' .. '9' -> "It's a digit"
        in 'a' .. 'z', in 'A' .. 'Z' -> "It's a letter"
        else -> "I don't klnow.. sorry TT"
    }

  • for
    for(i in 0..9) print("$i ")

    for(i in 10 downTo 1) print("$i ")

    for(i in 10 downTo 1 step 2) print("$i ")

    var array: Array<int> = arrayOf(1,2,3)
    for(i in 0 until array.size) print(array[1])

고차 함수와 람다

  • 고차 함수 (High Order Function) : 매개변수 또는 반환 값으로 또 다른 함수가 사용되는 함수
    button.setOnClickListener({ /* Click Event 함수 내용 */ })
  • 람다(Lambdas) 식의 문법 {매개변수1: 타입, 매개변수2: 타입.. -> 반환형}
    val sum = {x:Int, y:Int -> x+y }

    val sum:(Int, Int) -> Int = {x,y -> x+y}
  • 익명함수 (Anonymous Function)
    Calculator(2,1,{a:Int, b:Int -> a+b})

    fun Calculator(a:Int, b:Int, p:(Int, Int) -> Int){
        println("$a, $b -> ${p(a, b)}")
    }

Collection

  • List ; listOf : mutableListOf, arrayListOf
  • Set ; set : mutableSetOf, hashSetOf, linkedSOf, sortedSetOf
  • Map ; mapOf : mutableMapOf, hashMapOf, linkedMapOf, sortedMapOf

  • 속성 val size:Int

  • 함수 fun contains(element:E):Boolean fun get(index:Int):E fun indexOf(element:E):Int fun isEmpty():Boolean fun subList(formIndex:Int, toIndex:Int):List

  • 확장 기능
  • MutableCollection 메서드

연산자 오버로딩 (operator)

  • 이항연산자 a+b a.plus(b) a-b a.minus(b) a*b a.times(b) a/b a.div(b) a%b a.rem(b)

  • 단항연산자 +a a.unaryPlus() -a a.unaryMinus() !a a.not() ++a, a++ a.inc() –a, a– a.dec()

  • 복합 대입 연산자 a+=b a.plusAssign(b) a-=b a.minusAssgin(b) a*=b a.timesAssign(b) a/=b a.divAssign(b) a%=b a.remAssign(b)

  • 비교 연산자 a==b a?.equals(b) ?: (b===null) a!=b !(a?.equals(b) ?: (b===null)) a>b a.compareTo(b) > 0 a<b a.comapreTo(b) < 0 a>=b a.compareTo(b) >= 0 a<=b a.compareTo(b) <= 0

  • 기타 in a.contain(b) .. a.rangeTo(b) a() a.invoke()

클래스 위임 (Class Delegation)

  • by : 상속의 단점을 보완하기 위해 사용되는 데코레이터 패턴(Decorator Pattern) 의 구현을 줄이기 위해 제공

    class CountingSet<T>: MutableCollection<T> {

    }


    class CountingSet<T>(val innerSet:MutableCollection<T>) : MutableCollection<T> by innerSet {

        override fun addAll(c:Collection<T>) : Boolean {
            .......
        }
    }


    fun main(args: Array<String>) {
        val lset = CountingSet<Int>(mutableListOf())
        val hset = CountingSet<Int>(hashSetOf())

        lset.addAll(listOf(1,2,3,2)) //override
        hset.addAll(listOf(1,2,3,2)) //source
    }

위임 프로퍼티 (Delegated Property)

  • 위임 프로퍼티란, 프로퍼티의 접근자 게터(getter)와 세터(setter)를 다른 객체로 위임하는 방식
  • 위임 받는 클래스에는 getValue() 메서드가 반드시 구현되어야 하며, 수정 가능한 var 타입의 경우 setValue() 메서드 역시 구현되어 있어야 한다.

  • by
    class Delegator(var value: Int){
        operator fun getValue(thisRef:Any?, property:KProperty<*>): Int {
            println("${property.name} get! $value")
            return value
        }
        operator fun setValue(thisRef:Any?, property:KProperty<*>, newValue: Int){
            println("${property.name} set! $newValue")
            value = newValue
        }
    }

    class Person(val name: String, age:Int, salary:Int) {
        var age:Int by Delegator(age)
        var salary:Int by Delegator(salary)
    }

    fun main(args:Array<String>?){
        val p = Person("test", 20, 2000)
        p.age = 21
        p.salary = 2100
        println("${p.name} - age:${p.age}, salary:${p.salary}")
    }
  • by Lazy() 지연 초기화를 위해 lazy() 메서드로 프로퍼티 접근을 위임할 수 있다.
    data class Address(val name:String, var phone:String, var addr: String=""){

    }

    fun loadAddrBook(p:Person): List<Address> {
        return listOf(/* List */)
    }

    class Person(val name: String) {
        var addrBook by Lazy({loadAddrBook(this)})
    }

    fun main(args:Array<String>?){
        val p = Person("test")
        p.addrBook
    }
  • Observable 프로퍼티 변경시 필요한 기능을 Delegates, observable() 메서드에 람다 식과 함께 위임할 수 있다.

  • Map Map, MutableMap 인터페이스에는 프로퍼티명 (property.name)을 이용한 getValue()와 setValue()가 구현되어 있어 위임 프로퍼티에 사용할 수 있다.

고차 함수와 람다의 활용

  • 고차함수란 다른 함수를 인자로 받거나 함수를 반환하는 함수
  • 람다 식의 정의에는 매개변수의 타입과 반환 타입이 명시되어야 하며, 매개 변수와 반환 타임은 Nullable이 될수 있다.
  • 고차 함수 정의시 매개변수 또는 반환 타입의 함수 타입을 명확하게 정의해야 하며, 디폴트 값을 지정할 수 있다.
  • 고차 함수와 람다를 이용해 소스 코드의 중복을 상당 부분 제거할 수 있다는 장점이 있다.

인라인 함수 (Inline Functions)

  • 인라인 함수는 컴파일 단계에서 호출 방식이 아닌 코드 자체가 복사되는 방식으로 변환되며, 함수 앞에 inline 키워드를 붙여 사용한다.
  • 람다 전달시 발생하는 메모리, 호출등의 오버 헤드를 감소시키기 위해 인라인 함수가 사용된다.
  • 특정 람다를 인라인 방식에서 제외하고 싶을때는 noinline 키워드를 사용한다.
    inline fun calculator(a: Int, b: Int, op: (Int, Int)->Int): Int {
        println("calculator body")
        return op(a,b)
    }

    fun main(args:Array<String>?){
        val result = calculator(1,2) {a:Int, b:Int -> a+b}
        println(result)
    }

- noinline : 인라인 함수 선언시 해당 파라미터 앞에 noinline 키워드를 붙여줌


$ calculator body
$ 3

람다에서의 리턴 (Return)

  • Non-local Return
  • 레이블을 이용한 Local Return
  • 코틀린에서 return은 Non-Local return 과 Local return 이 있으며, Non-local return은 람다 안에 있는 return 구문이 바깥족 함수를 반환시키는 것을 의미한다.
  • 람다 식에서는 return을 사용할 수 없지만, 인라인 함수에 사용되는 람다의 경우 Non-local return을 사용할 수 있다.
  • 람다 식에서 Local return을 사용하고 싶다면 레이블을 이용해 사용할 수 있다.
  • 일반적인 Local return을 사용할 수 있는 익명 함수를 람다 대신 사용할 수 있다.