Kotlin

[Kotlin] 코틀린 문법 정리 - 03

728x90

 

 

1. Inline 함수

 Inline 함수는 함수 호출문을 함수의 몸체로 대체하기 때문에 성능을 조금이나마 개선할 수 있다.

inline fun hello(){
    println("hello")
    println("kotlin")
}

fun main(args: Array<String>) {
    hello()
    hello()
    hello()
}

 Inline 함수를 호출하면 컴파일 되는 순간 아래와 같은 코드로 대체된다.

fun main(args: Array<String>) {
    println("hello")
    println("kotlin")
    println("hello")
    println("kotlin")
    println("hello")
    println("kotlin")
}

 

 

2. lateinit

 lateinit 키워드가 붙은 프로퍼티는 클래스 안에서 바로 초기화하지 않아도 된다. lateinit은 var 프로퍼티에만 붙일 수 있다.

class Person {
    lateinit var name: String

    fun print() {
        println(name)
    }
}

fun main(args: Array<String>) {
    val person = Person()

    person.name = "Jack"
    person.print()
}

 lateinit 프로퍼티가 초기화되었는지 알려면 isInitialized 함수를 사용하면 된다. 만약 name 프로퍼티에 값을 지정하지 않은 채 프로퍼티에 접근하면 UninitializedPropertyAccessException 예외가 발생한다.

 

 

3. Nullable 리시버

 확장 함수를 응용하면, 참조 변수에 null이 지정되어 있어도 함수 호출이 가능하게 할 수 있다.

fun String?.isNumber() {
    if (this == null) {
        println("문자열이 null입니다.")
    }
}

fun main(args: Array<String>) {
    val empty: String? = null
    empty.isNumber()
}

 empty는 String? 타입이고, null이 지정되어 있다. isNumber 확장 함수는 리시버 타입이 Nullable이기 때문에, 표현식의 값이 null이어도 isNumber 확장 함수를 호출할 수 있다. 

 

 

4. 확장 함수의 리시버 타입이 상속 관계에 있을 때

open class AAA

class BBB : AAA()

fun AAA.hello() = println("AAA")
fun BBB.hello() = println("BBB")

fun main(args: Array<String>) {
    val test: AAA = BBB()
    test.hello() // AAA
}

 타입은 AAA이나, 실제로는 BBB 객체를 가리키고 있다. 확장 함수는 멤버 함수와는 다르게 참조 변수가 실제로 가리키는 객체의 타입을 따르지 않고, 참조 변수의 타입을 그대로 따른다.

 

 

5. 중첩 클래스 (Nested Class)

 클래스 안에 또 다른 클래스를 선언할 수 있다.  

class Outer {
    class Nested {
        fun hello() = println("hello")
    }
}
fun main(args: Array<String>){
    val instance: Outer.Nested = Outer.Nested()

    instance.hello()
}

 Nested 클래스의 멤버 함수는 Outer 클래스의 프로퍼티나 멤버 함수에 접근할 수 없다.

class Outer {
    private val name = "Jack"

    class Nested {
        fun printName() = println(name) // 오류
    }
}

 

 

6. 내부 클래스 (Inner Class)

 중첩 클래스가 단순히 식별자만 바깥 클래스에 속해있는 것이었다면, 내부 클래스는 인스턴스가 바깥 클래스의 인스턴스에 완전히 소속된다. 내부 클래스는 this@Outer 키워드를 이용하여 자신이 속한 바깥 클래스의 인스턴스에 접근할 수 있다.

class Outer(private val value: Int) {
    fun print() {
        println(this.value)
    }

    inner class Inner(private val innerValue: Int) {
        fun print() {
            this@Outer.print()
            println(this.innerValue + this@Outer.value)
        }
    }
}

fun main(args: Array<String>) {
    val innerInstance: Outer.Inner = Outer(610).Inner(40)

    innerInstance.print()
}

 

 

7. 데이터 클래스 (Data Class)

 코틀린은 데이터에 특화된 클래스를 선언할 수 있는 문법을 제공한다. 클래스를 데이터 클래스로 선언하면, 다음과 같은 이점이 생긴다.

  • Any 클래스에 들어있는 equals, hashCode, toString 멤버 함수가 자동으로 오버라이딩 된다.
  • equals 멤버 함수는 각 프로퍼티의 값이 서로 모두 같으면 true를 반환한다.
  • 객체를 복사하는 copy 함수가 자동으로 선언된다.

클래스를 데이터 클래스로 선언하기 위해선 다음의 규칙들을 지켜야 한다.

  • 적어도 하나의 프로퍼티를 가져야 한다.
  • 프로퍼티에 대응하지 않는 생성자 매개변수를 가질 수 없다.
  • abstract, open, sealed, inner 키워드를 붙일 수 없다.
  • 인터페이스만 구현할 수 있다. 코틀린 1.1 버전부터는 sealed 클래스도 상속 가능하다.
  • component1, component2, ... 와 같은 이름으로 멤버 함수를 선언할 수 없다. 컴파일러가 내부적으로 사용하는 이름이기 때문이다.
  •  
data class Employee(val name: String, val age: Int, val salary: Int)

fun main(args: Array<String>) {
    val first = Employee("a", 30, 2000)
    val second = Employee("b", 24, 5000)
    val third = first.copy()

    println(first.toString())
    println(third.toString())
    println(first == second) // false
    println(first == third) // true
    println(first === third) // false
}

 

 

8. 객체 분해하기

 데이터 클래스의 인스턴스에 한해, 객체를 여러 개의 변수로 쪼개는 것이 가능하다.

data class Employee(val name: String, val age: Int, val salary: Int)

fun main(args: Array<String>) {
    val(name, _, salary) = Employee("a", 20, 2000)
    println("$name $salary")
}

 

 

9. 함수 리터럴과 람다식

fun main(args: Array<String>) {
    val instantFunc: (Int) -> Unit
    instantFunc = { number: Int ->
        println("hello $number")
    }

    instantFunc(33)
    instantFunc.invoke(33)
}

 위와 같이 함수를 저장할 수 있는 타입을 함수 타입이라고 한다. 함수 타입의 변수는 invoke 멤버 함수를 통해서도 호출할 수 있다. instanceFunc?.invoke(33) 와 같이 쓸 수 있으므로 null 처리를 하기 편해진다.

 

 

10. 익명 함수 (Anonymous Function)

fun main(args: Array<String>) {
    val instantFunc: (Int) -> Unit = fun(number: Int): Unit {
        println("hello $number")
    }

    instantFunc(33)
    instantFunc.invoke(33)
}

 람다식으로 된 함수 리터럴을 익명 함수의 형태로도 표현할 수 있다.

 

 

11. it 식별자

 매개변수를 생략하면 it이라는 특별한 식별자가 만들어진다. 여기서 it은 생략한 Int 타입의 매개변수를 대체한다.

fun main(args: Array<String>) {
    val instantFunc: (Int) -> Unit = {
        println("hello $it")
    }

    instantFunc(33)
}

 

 

12. 함수 참조 (Function Reference)

 함수 타입의 변수는 이미 선언되어 있는 함수나 객체의 멤버 함수를 가리킬 수도 있다.

fun plus(a: Int, b: Int) = println("plus 호출됨 ${a + b}")

object Object {
    fun minus(a: Int, b: Int) = println("object minus 호출됨 ${a - b}")
}

class Class {
    fun average(a: Int, b: Int) = println("class average 호출됨 ${(a + b) / 2}")
}

fun main(args: Array<String>) {
    var instantFunc: (Int, Int) -> Unit
    instantFunc = ::plus
    instantFunc(60, 28) // 88

    instantFunc = Object::minus
    instantFunc(60, 28) // 32

    instantFunc = Class()::average
    instantFunc(60,28) // 44
}

 

 

13. 고차 함수

 고차 함수란, 인수로 함수를 받거나, 함수를 반환하는 함수를 뜻한다.

fun decorate(task: () -> Unit) {
    println("=== 작업 시작 ===")
    task()
    println("=== 작업 완료 ===")
}

fun main(args: Array<String>) {
    decorate {
        val a = 10
        val b = 5

        println("$a + $b = ${a + b}")
    }
}

 

 

14. 클로저 (Closure)

 클로저는 내부 scope를 뛰어 넘어서 하위 함수가 상위 함수의 접근할 수 있는 것을 말한다. 리터럴이 만들어지는 순간, 함수 리터럴은 자기 주변의 상황을 함께 저장한다. 즉, 함수가 만들어질 때 name 매개변수의 값을 복사해 갖고 있는다.

fun main(args: Array<String>) {
    var name = "kim"
    fun closureTest() {
        println(name)   //close over 접근  
    }
    closureTest()   //kim 출력  
}

 

 

15. 리시버가 붙은 함수 리터럴

 함수 리터럴에 리시버를 적용하여 확장 함수처럼 만들 수 있다.

fun main(args: Array<String>) {
    val min: Int.(Int) -> Int = { value ->
        if (this < value) this
        else value
    }
    
    println(25.min(20))
}

 

 

출처 : 초보자를 위한 코틀린 200제 / 엄민석
728x90