003 Kotlin Basic

코틀린 문법 한번에 정리하기 #

  • 주석 정리

Variable #

// top-level
var x = 5

fun main() {
    x+= 1
    println(x)


    val a : Int = 1

    val b = 1

    val c : Int
    c = 3

    val d : Int
    d = 123

    //val(value) : 불변(Immutable)
    //var(variable) : 가변(Mutable)

    var e : String = "Hello"
    e = "World"

    var f = 123
   // f = "hi" // 컴파일 오류 타입은 변경이 불가
}

Function #

// 기본적인 함수 선언 스타일
fun sum(a: Int, b: Int) : Int {
    return a + b
}

// 표현식 스타일
fun sum2(a: Int, b: Int) : Int = a + b

// 표현식 & 반환타입 생략
fun sum3(a: Int, b: Int) = a + b


// 몸통이 있는 함수는 반환 타입을 제거하면 컴파일 오류
fun sum4(a: Int, b: Int) : Int {
    return a + b
}

// 반환타입이 없는 함수는 Unit을 반환한다
fun printSum(a: Int, b: Int) : Unit {
    println("$a + $b = ${a + b}")
}

// 디폴트 파라미터
fun greeting(message: String = "안녕하세요!!") {
    println(message)
}
//
//fun main( ) {
//    greeting()
//    greeting("HI!!!")
//}


fun log(level: String = "INFO", message: String) {
    println("[$level]$message")
}

fun main( ) {
    log(message = "인포 로그")
    log(level = "DEBUG", "디버그 로그")
    log("WARN", "워닝 로그")
    log(level = "ERROR", message = "에러 로그")
}

For #

fun main() {

    // 범위 연산자 .. 를 사용해 for loop 돌리기
    for (i in 0..3) {
        println(i)
    }

    // until 을 사용해 반복한다
    // 뒤에 온 숫자는 포함하지 않는다
    for (i in 0 until 3) {
        println(i)
    }

    // step 에 들어온 값 만큼 증가시킨다
    for ( i in 0..6 step 2) {
        println(i)
    }

    // downTo를 사용해 반복하면서 값을 감소시킨다
    for (i in 3 downTo 1) {
        println(i)
    }

    // 전달받은 배열을 반복
    val numbers = arrayOf(1,2,3)

    for (i in numbers) {
        println(i)
    }
}

If #

fun main() {
    //if..else 사용
    val job = "Software Developer"

    if (job == "Software Developer") {
        println("개발자")
    } else {
        println("개발자아님")
    }

    //코틀린의 if...else는 표현식이다
    val age : Int = 10

    val str = if (age > 10) {
        "성인"
    } else {
        "아이"
    }

    //코틀린은 삼항 연산자가 없다. if..else가 표현식이므로 불필요하다
    val a = 1
    val b = 2
    val c = if (b > a) b else a

}

When #

fun main() {
    // 자바 코드를 코틀린의 when식으로 변환한 코드
    val day = 2

    val result = when (day) {
        1 -> "월요일"
        2 -> "화요일"
        3 -> "수요일"
        4 -> "목요일"
        else -> "기타"
    }
    println(result)

    // else를 생략할 수 있다
    when(getColor()) {
        Color.RED -> print("red")
        Color.GREEN -> println("green")
        else -> println("blue")
    }

    // 여러개의 조건을 콤마로 구분해 한줄에서 정의할 수 있다
    when (getNumber()) {
        0, 1 -> print("0 또는 1")
        else -> print("0 또는 1이 아님")
    }
}

enum class Color {
    RED, GREEN, BLUE
}

fun getColor() = Color.RED

fun getNumber() = 2

while #

fun main() {

    // 자바의 while문과 동일
    // 조건을 확인하고 참이면 코드 블록을 실행한 후 다시 조건을 확인
    var x = 5

    while (x > 0) {
        println(x)
        x--
    }
}

Exception #

fun main() {

    try {
        throw Exception()
    } catch (e: Exception) {
        println("에러 발생!")
    } finally {
        println("finally 실행!")
    }

    val a = try {
        "1234".toInt()
    } catch (e: Exception) {
        println("예외 발생 !")
    }
    println(a)

    //throw Exception("예외 발생!")

    val b: String? = null
    val c: String = b ?: failFast("a is null")

    println(c.length)
}

fun failFast(message: String): Nothing {
    throw IllegalArgumentException(message)
}

Null Safety #

fun getNullStr(): String? = null

fun getLengthIfNotNull(str: String?) = str?.length ?: 0

fun main() {

    val nullableStr = getNullStr()

    val nullableStrLength = nullableStr?.length ?: "null인 경우 반환".length
    println(nullableStrLength)

    val length = getLengthIfNotNull(null)
    println(length)


    //throw NullPointerException()

//    val c: String? = null
//    val d = c!!.length

//    println(Java_NullSafety.getNullStr()?.length ?: 0)
}

Class Property #

class Coffee(
    var name: String = "",
    var price: Int = 0,
    var iced: Boolean = false,
) {

    val brand: String
        get() {
            return "스타벅스"
        }

    var quantity : Int = 0
        set(value) {
            if (value > 0) { // 수량이 0 이상인 경우에만 할당
                field = value
            }
        }

}

class EmptyClass

fun main() {
    val coffee = Coffee()
    coffee.name = "아이스 아메리카노"
    coffee.price = 2000
    coffee.quantity = 1
    coffee.iced = true

    if (coffee.iced) {
        println("아이스 커피")
    }
    println("${coffee.brand} ${coffee.name} 가격은 ${coffee.price} 수량은 ${coffee.quantity}")
}

Inheritance #

open class Dog {
    open var age: Int = 0

    open fun bark() { // 반드시 오버라이드해야하는건 아니다. open fun일 경우 반드시 body를 구현해야한다.
        println("멍멍")
    }
}

open class Bulldog(final override var age: Int = 0) : Dog() {

    final override fun bark() {
        super.bark()
    }
}

abstract class Developer {

    abstract var age: Int
    abstract fun code(language: String)

}

class BackendDeveloper(override var age : Int) : Developer() {

    override fun code(language: String) {
        println("I code with $language")
    }
}

fun main() {
    val backendDeveloper = BackendDeveloper(age = 20)
    println(backendDeveloper.age)
    backendDeveloper.code("Kotlin")


    val dog = Bulldog(age = 2)
    println(dog.age)
    dog.bark()

}

Interface #

class Product(val name: String, val price: Int)

interface Wheel {
    fun roll()
}

interface Cart : Wheel {

    var coin: Int

    val weight: String
        get() = "20KG"

    fun add(product: Product)

    fun rent() {
        if (coin > 0) {
            println("카트를 대여합니다")
        }
    }

    override fun roll() {
        println("카트가 굴러갑니다")
    }

    fun printId() = println("1234")
}

interface Order {

    fun add(product: Product) {
        println("${product.name} 주문이 완료되었습니다")
    }

    fun printId() = println("5678")

}

class MyCart(override var coin: Int) : Cart, Order {

    override fun add(product: Product) {
        if (coin <= 0) println("코인을 넣어주세요")
        else println("${product.name}이(가) 카트에 추가됐습니다")

        // 주문하기
        super<Order>.add(product)
    }

    override fun printId() {
        super<Cart>.printId()
        super<Order>.printId()
    }

}

fun main() {
    val cart = MyCart(coin = 100)
    cart.rent()
    cart.roll()
    cart.add(Product(name = "장난감", price = 1000))
    cart.printId()
}

Collection #

import java.util.*
import java.util.stream.Collectors
import kotlin.collections.ArrayList

fun main() {

    // immutable
    val currencyList = listOf("달러", "유로", "원")


    // mutable
    val mutableCurrencyList: MutableList<String> = mutableListOf<String>().apply {
        add("달러")
        add("유로")
        add("원")
    }

    mutableCurrencyList.add("파운드")


    // immutable set
    val numberSet = setOf(1, 2, 3, 4)

    // mutable set
    val mutableSet = mutableSetOf<Int>().apply {
        add(1)
        add(2)
        add(3)
        add(4)
    }


    // immutable map
    val numberMap = mapOf("one" to 1, "two" to 2)

    // mutable map
    val mutableNumberMap = mutableMapOf<String, Int>()
    mutableNumberMap["one"] = 1
    mutableNumberMap["two"] = 2
    mutableNumberMap["three"] = 3



    // 컬렉션 빌더는 내부에선 mutable 반환은 immutable
    val numberList: List<Int> = buildList{
        add(1)
        add(2)
        add(3)
        add(4)
    }


    // linkedList
    val linkedList = LinkedList<Int>().apply {
        addFirst(3)
        add(2)
        addLast(1)
    }

    // arrayList
    val arrayList = ArrayList<Int>().apply {
        add(1)
        add(2)
        add(3)
    }


//    val iterator = currencyList.iterator()
//    while (iterator.hasNext()) {
//        println(iterator.next())
//    }
//
//    println("===============")
//
//    for (currency in currencyList) {
//        println(currency)
//    }
//
//    println("===============")
//
//    currencyList.forEach {
//        println(it)
//    }

    // for loop -> map
    val lowerList = listOf("a","b","c")
    //val upperList = mutableListOf<String>()

//    for (lowerCase in lowerList) {
//        upperList.add(lowerCase.uppercase())
//    }

    val upperList = lowerList.map { it.uppercase() }

    //val filteredList = mutableListOf<String>()
//    for (upperCase in upperList) {
//        if (upperCase == "A" || upperCase == "C" ) {
//            filteredList.add(upperCase)
//        }
//    }
    val filteredList = upperList
        .asSequence() // 대량데이터의 경우 이걸 사용하는게 좋다.
        .filter { it == "A" || it == "C" }
        .toList()

    println(filteredList)
}

Data Calss #

data class Person(val name: String, val age: Int) {

}

fun main() {
    val person1 = Person(name = "tony", age = 12)

    val (name, age) = person1

    println("이름=${name}, 나이=${age}")
//    val set = hashSetOf(person1)
//    println(set.contains(person1))

}

Singleton #

import java.time.LocalDateTime

//object Singleton {
//
//    val a = 1234
//
//    fun printA() = println(a)
//}
//
//fun main() {
//    println(Singleton.a)
//    Singleton.printA()
//}
//object DatetimeUtils {
//
//    val now : LocalDateTime
//        get() = LocalDateTime.now()
//
//    const val DEFAULT_FORMAT = "YYYY-MM-DD"
//
//    fun same(a: LocalDateTime, b: LocalDateTime) : Boolean {
//        return a == b
//    }
//
//}
//
//fun main() {
//    println(DatetimeUtils.now)
//    println(DatetimeUtils.now)
//    println(DatetimeUtils.now)
//
//    println(DatetimeUtils.DEFAULT_FORMAT)
//
//    val now = LocalDateTime.now()
//    println(DatetimeUtils.same(now, now))
//}

class MyClass {

    private constructor()

    companion object MyCompanion {
        val a = 1234

        fun newInstance() = MyClass()

    }
}

fun main() {
    println(MyClass.a)
    println(MyClass.newInstance())

    println(MyClass.a)
    println(MyClass.newInstance())
}

Sealed Class #

sealed class Developer {

    abstract val name: String
    abstract fun code(language: String)

}

data class BackendDeveloper(override val name: String) : Developer() {

    override fun code(language: String) {
        println("저는 백엔드 개발자입니다 ${language}를 사용합니다")
    }
}

data class FrontendDeveloper(override val name: String) : Developer() {

    override fun code(language: String) {
        println("저는 프론트엔드 개발자입니다 ${language}를 사용합니다")
    }
}

object OtherDeveloper : Developer() {

    override val name: String = "익명"

    override fun code(language: String) {
        TODO("Not yet implemented")
    }

}

data class AndroidDeveloper(override val name: String) : Developer() {

    override fun code(language: String) {
        println("저는 안드로이드 개발자입니다 ${language}를 사용합니다")
    }
}


data class IosDeveloper(override val name: String) : Developer() {

    override fun code(language: String) {
        println("저는 Ios 개발자입니다 ${language}를 사용합니다")
    }
}

object DeveloperPool {
    val pool = mutableMapOf<String, Developer>()

    // 컴파일러는 Developer 구현 클래스가 무엇인지를 모름
    // else가 없으면 when절에 컴파일 오류남
    // Developer 를 sealed Class로 정의하면 else문 생략가능
    // 같은 패키지/하위 모듈에 있는 경우에만 sealed class의 하위클래스가 될 수 있다
    // 컴파일러가 Developer 의 자식클래스를 알고있끼 때문이다.
    fun add(developer: Developer) = when(developer) {
        is BackendDeveloper -> pool[developer.name] = developer
        is FrontendDeveloper -> pool[developer.name] = developer
        is AndroidDeveloper ->  pool[developer.name] = developer
        is IosDeveloper ->  pool[developer.name] = developer
        is OtherDeveloper -> println("지원하지않는 개발자종류입니다")
    }

    fun get(name: String) = pool[name]
}

fun main() {
    val backendDeveloper = BackendDeveloper(name="토니")
    DeveloperPool.add(backendDeveloper)

    val frontendDeveloper = FrontendDeveloper(name="카즈야")
    DeveloperPool.add(frontendDeveloper)

    val androidDeveloper = AndroidDeveloper(name="안드로")
    DeveloperPool.add(androidDeveloper)

    println(DeveloperPool.get("토니"))
    println(DeveloperPool.get("카즈야"))
    println(DeveloperPool.get("안드로"))


}

Extension #

/**
 * 문자열 첫번째 원소 리턴
 */
fun String.first() : Char {
    return this[0]
}

fun String.addFirst(char: Char) : String {
    // this : 수신자 객체
    return char + this.substring(0)
}

class MyExample {
    fun printMessage() = println("클래스 출력")
}

// MyExample의 확장함수 생성
// printMessage 멤버함수와 이름을 동일하게 했을때 멤버함수가 우선적으로 수행된다.
// 확장함수의 멤버함수와 동일한 시그니처는 멤버함수가 실행됨
fun MyExample.printMessage() = println("확장 출력")

// 시그니처가 다르면 확장함수 실행이 잘 됨
fun MyExample.printMessage(message:String) = println(message)

// MyExample 이 null일 가능성이 존재
// null인 경우와 아닌 경우 분기처리
fun MyExample?.printNullOrNotNull() {
    if (this == null) println("널인 경우에만 출력")
    else println("널이 아닌 경우에만 출력")
}

fun main() {
    var myExample: MyExample? = null
    // 함수에서 null 체크를 하고있다는걸 컴파일러가 알고있어서 오류가 안난다.
    myExample.printNullOrNotNull()

    myExample = MyExample()
    myExample.printNullOrNotNull()

    //MyExample().printMessage("확장 출력")

//    println("ABCD".first())
//
//    println("ABCD".addFirst('Z'))
}

Generics #

class MyGenerics<out T>(val t: T) { // 공변성은 자바 제네릭의 extends 코틀린에선 out

}

class Bag<T> {

    fun saveAll(
        to: MutableList<in T>, // 반공변성은 자바 제네릭의 super 코틀린에선 in
        from: MutableList<T>,
    ) {
        to.addAll(from)
    }
}

fun main() {
    val bag = Bag<String>()
    // String이 CharSequence의 하위타입인데,
    // 반공변성에서는 mutableListOf<CharSequence>가 mutableListOf<String>의 하위타입이 된다.
    bag.saveAll(mutableListOf<CharSequence>("1", "2"), mutableListOf<String>("3", "4"))

    // MyGenerics<CharSequence> 가 MyGenerics<String> 보다 상위타입
    val generics = MyGenerics<String>("테스트")
    val charGenerics : MyGenerics<CharSequence> = generics

    // 제네릭을 사용한 클래스의 인스턴스를 만드려면 타입아규먼트를 제공 (컴파일러 타입 추론 가능)
    val generics2 = MyGenerics<String>("테스트")

    // 생략가능
    val generics3 = MyGenerics("테스트")

    // 변수의 타입에 제네릭을 사용한 경우
    val list1: MutableList<String> = mutableListOf()
    // 타입아규먼트를 생성자에서 추가
    val list2 = mutableListOf<String>()

    val list3 : List<*> = listOf<String>("테스트")
    val list4: List<*> = listOf<Int>(1, 2, 3, 4)

    // PECS는 Producer-Extends, Consumer-Super
    // 공변성은 자바 제네릭의 extends 코틀린에선 out
    // -> 공변성 : T’가 T의 서브타입이면, C<T’>는 C<out T>의 서브타입이다.
    // 반공변성은 자바 제네릭의 super 코틀린에선 in
    // -> 반공변성 : T’가 T의 서브타입이면, C<T>는 C<in T’>의 서브타입이다.

}

Late init #

class `7_LateInit` {
    // 가변 프로퍼티에 대한 지연 초기화
    // nullable이 아님에도 초기호 안했어도 컴파일 오류가 발생하지 않는다.
    lateinit var text: String // var : 가변

    val textInitialized: Boolean
        // isInitialized 는 클래스 내부에서만 사용 가능하다. (Main 등에서 사용 불가능)
        get() = this::text.isInitialized // 초기화 여부

    fun printText() {
        println(text)
    }
}

fun a (str:String, block: (String) -> Unit) {
    block(str)
}
fun main() {

    "".let {  }
    a("") {
        it.length
    }
    val test = `7_LateInit`()

    if (!test.textInitialized) {
        test.text = "하이요"
    }
    test.printText() // 초기화 전에 출력 요청하면 오류 발생

}

Lazy init #

class HelloBot {

    // val 불변
    // by lazy 사용 (멀티쓰레드 안전)
    // 기본 :  LazyThreadSafetyMode.SYNCHRONIZED
    // LazyThreadSafetyMode.NONE 등 상태값을 설정하여 쓰레드 안전성 무시 가능
    // 불변을 유지하면서 변수에 대한 초기화를 뒤로 미룰수 있다.
    // 변수가 처음으로 사용될 때까지 해당 변수의 초기화를 늦춘다.
    // -> 즉, 변수에 처음으로 접근하는 시점에서 초기화 코드가 실행
    val greeting: String by lazy(LazyThreadSafetyMode.PUBLICATION) {
        // 멀티쓰레드 환경에서도 동기화가 필요하지 않을때 : LazyThreadSafetyMode.PUBLICATION
        // 한 번 초기화된 이후에는 모든 스레드가 같은 값을 공유
        // 따라서 여러 스레드에서 동시에 초기화를 시도하더라도 최초 한 번만 초기화가 이루어지고 나면 이후에는 초기화 코드가 다시 실행되지않음
        getHello()
    }

    fun sayHello() = println(greeting)
}

fun getHello() = "안녕하세요"

fun main() {
    val helloBot = HelloBot()
    // 초기화 이후에는 더이상 by lazy {}을 수행하지 않음

    // ...
    // ...
    for (i in 1..5) {
        Thread { // 쓰레드 생성하여 병렬로 수행해보자
            helloBot.sayHello() // 변수 초기화 첫 실행 
        }.start()
    }

}

Pair Destructuring #

// f((1, 3)) = 1 + 3 = 4
// f(1, 3) = 1 + 3 = 4

//data class Tuple(val a : Int, val b: Int)

fun plus(pair: Pair<Int, Int>) = pair.first + pair.second

fun main() {
    //println(plus(1,3))
    val plus = plus(Pair(1, 3))
    println(plus)

    val pair = Pair("A", 1) // 불변
    val newPair = pair.copy(first = "B") // 새로운 Pair을 생성
    println(newPair)

    val second = newPair.component2() // second 값 가져오기
    println(second)

    val list = newPair.toList()
    println(list)

    /* 3개 요소 (4개 이상부터는 지원하지 않음, Collection 사용하면 됨) */
    val triple = Triple("A","B","C")
    println(triple) // 출력

    triple.first
    triple.second
    val newTriple = triple.copy(third = "D") // third 값 변경한 새로운 Triple 생성

    println(newTriple)

    println(newTriple.component3())

    /* 구조분해 할당 */
    val (a: String, b: String, c: String) = newTriple
    println("$a, $b, $c")

    val list3: List<String> = newTriple.toList()
    val (a1, a2, a3) = list3
    println("$a1, $a2, $a3")

    list3.component1()
    list3.component2()
    list3.component3()
//    list3.component4()
//    list3.component5()

    val map = mutableMapOf(Pair("이상훈", "개발자"))
    for ( (key, value) in map ) {
        println("${key}의 직업은 $value")
    }
}

범위 지정 함수 #

img.png

(이미지 출처 : https://medium.com/@limgyumin/%EC%BD%94%ED%8B%80%EB%A6%B0-%EC%9D%98-apply-with-let-also-run-%EC%9D%80-%EC%96%B8%EC%A0%9C-%EC%82%AC%EC%9A%A9%ED%95%98%EB%8A%94%EA%B0%80-4a517292df29)

Run #

  • run 정의
inline fun <T, R> T.run(block: T.() -> R): R {
    return block()
}
  • 예제코드
class DatabaseClient {
    var url: String? = null
    var username: String? = null
    var password: String? = null

    // DB에 접속하고 Boolean 결과를 반환
    fun connect(): Boolean {
        println("DB 접속 중 ...")
        Thread.sleep(1000)
        println("DB 접속 완료")
        return true
    }
}

fun main() {

//    val config = DatabaseClient()
//    config.url = "localhost:3306"
//    config.username = "mysql"
//    config.password = "1234"
//    val connected = config.connect()

    // run 안에서 수신자 객체 참조는 this로 함 (생략도 가능)
    // 변수 중복 참조를 생략할 수 있다는 장점 (위에서 config.xx가 반복됨)
    val connected: Boolean = DatabaseClient().run {
        url = "localhost:3306"
        username = "mysql"
        this.password = "1234"
        connect() // 자동 return
    }
    println(connected)


   val result: Boolean =  with(DatabaseClient()) {
        url = "localhost:3306"
        username = "mysql"
        password = "1234"
        connect()
    }
    println(result)

}

Also #

  • also 정의
inline fun <T> T.also(block: (T) -> Unit): T {
    block(this)
    return this
}
  • 예제
class User(val name: String, val password: String) {

    fun validate() {
        if (name.isNotEmpty() && password.isNotEmpty()) {
            println("검증 성공!")
        } else {
            println("검증 실패!")
        }
    }

    fun printName() = println(name)

}

fun main() {

    User(name = "tony", password = "1234").also {
        // it을 사용해서 간결하게 사용 가능
        it.validate()
        it.printName()
    }
}

Apply #

  • apply 정의
inline fun <T> T.apply(block: T.() -> Unit): T {
    block()
    return this
}
  • 예제코드
fun main() {
    // return 타입이 Context 객체에 대한 타입 그대로 (DatabaseClient)
    DatabaseClient().apply {
        url = "localhost:3306"
        username = "mysql"
        this.password = "1234"
    }.connect()
        .run { println(this) } // this : connect() 함수의 반환결과
}

Let #

  • let 정의
inline fun <T, R> T.let(block: (T) -> R): R {
    return block(this)
}
  • 예제코드
fun main() {

    val str: String? = "안녕"

    // str 이 null이 아닌 경우에 동작한다.
    val result: Int? = str?.let {
        println(it) // it = str

        val abc: String? = "abc"
        val def: String? = "def"
        if (!abc.isNullOrEmpty() && !def.isNullOrEmpty()) {
            println("abcdef가 null 아님")
        }

        // return 키워드 없이도 return 값으로 셋팅된다.
        1234
    }
    println(result)


//    val this: String? = null
//    val it : String? = null


    val hello = "hello"
    val hi = "hi"

    hello.let { a : String ->

        //println(a.length)

        hi.let{ b ->
            println(a.length)
            println(b.length)
        }
    }

}

With #

  • with 정의
inline fun <T, R> with(receiver: T, block: T.() -> R): R {
    return receiver.block()
}
  • 예제
fun main() {

    val str = "안녕하세요"

    //
    val length: Int = with(str) {
        length // return 생략 가능
    }
    println(length)
}