TDD, Clean Code Preview

TDD, Clean Code with Kotlin Preview

TDD, Clean Code with Kotlin ๊ธ€์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•œ ๊ธ€์ž…๋‹ˆ๋‹ค.

Start Kotlin

Kotlin Web Compiler Site

https://play.kotlinlang.org/


๋ณ€์ˆ˜์™€ ์ž๋ฃŒํ˜•

โœ… ๋ณ€์ˆ˜์˜ ์„ ์–ธ

var: ์ผ๋ฐ˜์ ์œผ๋กœ ํ†ต์šฉ๋˜๋Š” ๋ณ€์ˆ˜. ์–ธ์ œ๋“ ์ง€ ์ฝ๊ธฐ ์“ฐ๊ธฐ๊ฐ€ ๊ฐ€๋Šฅ val: ์„ ์–ธ์‹œ์—๋งŒ ์ดˆ๊ธฐํ™” ๊ฐ€๋Šฅ. ์ค‘๊ฐ„์— ๊ฐ’ ๋ณ€๊ฒฝ ๋ถˆ๊ฐ€

fun main() {
    var a: Int
    a = 123
    println(a) // 123
    
    var b: Int? = null // nallable ๋ณ€์ˆ˜
    b = null
    println(b) // null
}

๋ณ€์ˆ˜์˜ ์„ ์–ธ ์œ„์น˜์— ๋”ฐ๋ฅธ ์ด๋ฆ„

  • Property: ํด๋ž˜์Šค์— ์„ ์–ธ๋œ ๋ณ€์ˆ˜

  • Local Variable: ์ด์™ธ์˜ Scope ๋‚ด์— ์„ ์–ธ๋œ ๋ณ€์ˆ˜

| ์ฝ”ํ‹€๋ฆฐ์€ ๊ธฐ๋ณธ ๋ณ€์ˆ˜์—์„œ null์„ ํ—ˆ์šฉํ•˜์ง€ ์•Š๋Š”๋‹ค.

  • ๋ณ€์ˆ˜์— ๊ฐ’์„ ํ• ๋‹นํ•˜์ง€ ์•Š์€์ฑ„๋กœ ์‚ฌ์šฉํ•˜๊ฒŒ ๋˜๋ฉด ์ปดํŒŒ์ผ ์—๋Ÿฌ

โœ… ์ฝ”ํ‹€๋ฆฐ์˜ ๊ธฐ๋ณธ ์ž๋ฃŒํ˜•

์ž๋ฐ”์™€์˜ ํ˜ธํ™˜์„ ์œ„ํ•ด ์ž๋ฐ”์™€ ๊ฑฐ์˜ ๋™์ผ

fun main() {
    // ์ •์ˆ˜ํ˜•(8์ง„์ˆ˜ ํ‘œ๊ธฐ๋Š” ๋ฏธ์ง€์›)
    var intValue:Int = 1234 // 32๋น„ํŠธ ์ด๋‚ด์˜ 10์ง„์ˆ˜
    var longValue:Long = 1234L // 64๋น„ํŠธ Longํƒ€์ž… 10์ง„์ˆ˜
    var intValueByHex:Int = 0x1af // 16์ง„์ˆ˜
    var intValueByBin:Int = 0b10110110 // 2์ง„์ˆ˜
    
    // ์‹ค์ˆ˜ํ˜•
    var doubleValue:Double = 123.5 // ์‹ค์ˆ˜์˜ ๊ธฐ๋ณธ
    var doubleValueWithExp:Double = 123.5e10 // ํ•„์š” ์‹œ ์ง€์ˆ˜ ํ‘œ๊ธฐ๋ฒ• ์ถ”๊ฐ€
    var floatValue:Float = 123.5f // 16๋น„ํŠธ float

    // ๋ฌธ์žํ˜•(๋‚ด๋ถ€์ ์œผ๋กœ ๋ฌธ์ž์—ด์„ UTF-16 BE๋กœ ๊ด€๋ฆฌ. ๊ธ€์ž ํ•˜๋‚˜๊ฐ€ 2bytes ๋ฉ”๋ชจ๋ฆฌ ๊ณต๊ฐ„ ์‚ฌ์šฉ)
    var charValue:Char = 'a'
    var koreanCharValue:Char = '๊ฐ€'
    
    // ๋…ผ๋ฆฌํ˜•
    var booleanValue:Boolean = true
    
    // ๋ฌธ์ž์—ด
    val stringValue = "one line string test"
    val multiLineStringValue = """multiline
    string
    test"""
}

์ง€์›๋˜๋Š” ํŠน์ˆ˜๋ฌธ์ž


ํ˜•๋ณ€ํ™˜๊ณผ ๋ฐฐ์—ด

โœ… ํ˜•๋ณ€ํ™˜

์ฝ”ํ‹€๋ฆฐ์€ ํ˜•๋ณ€ํ™˜ ์‹œ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋Š” ์˜ค๋ฅ˜๋ฅผ ๋ง‰๊ธฐ ์œ„ํ•ด ์•”์‹œ์  ํ˜•๋ณ€ํ™˜์€ ๋ฏธ์ง€์›

fun main() {
    // ๋ช…์‹œ์  ํ˜•๋ณ€ํ™˜
    var a: Int = 54321
    var b: Long = a.toLong()
}

โœ… ๋ฐฐ์—ด

arrayOf, arrayOfNulls

fun main() {
    // ๊ฐ’์ด ์žˆ๋Š” ๋ฐฐ์—ด ์ƒ์„ฑ
    var intArr = arrayOf(1, 2, 3, 4, 5)
    
    // ํŠน์ • ํฌ๊ธฐ๋ฅผ ๊ฐ€์ง„ ๋น„์–ด์žˆ๋Š” ๋ฐฐ์—ด ์ƒ์„ฑ
    var nullArr = arrayOfNulls<Int>(5)
    
    intArr[2] = 8
    println(intArr[4])
}

ํƒ€์ž…์ถ”๋ก ๊ณผ ํ•จ์ˆ˜

โœ… ํƒ€์ž…์ถ”๋ก 

๋ณ€์ˆ˜ ํ•จ์ˆ˜๋“ค์„ ์„ ์–ธํ•  ๋•Œ๋‚˜ ์—ฐ์‚ฐ์ด ์ด๋ฃจ์–ด์งˆ ๋•Œ ์ž๋ฃŒํ˜•์„ ์ฝ”๋“œ์— ๋ช…์‹œํ•˜์ง€ ์•Š์•„๋„ ์ž๋™์œผ๋กœ ์ž๋ฃŒํ˜•์„ ์ถ”๋ก 

  • ๋ฐ˜๋“œ ํŠน์ •ํ•œ ์ž๋ฃŒํ˜•์œผ๋กœ ์ง€์ •ํ•ด์•ผํ•˜๋Š” ์ƒํ™ฉ์ด ์•„๋‹ˆ๋ผ๋ฉด ๋Œ€๋ถ€๋ถ„์€ ์ฝ”ํ‹€๋ฆฐ์˜ ํƒ€์ž…์ถ”๋ก  ๊ธฐ๋Šฅ์„ ์ด์šฉ

โœ… ํ•จ์ˆ˜

์ฝ”ํ‹€๋ฆฐ์—์„œ ํ•จ์ˆ˜๋Š” ๋‚ด๋ถ€์ ์œผ๋กœ ๊ธฐ๋Šฅ์„ ๊ฐ€์ง„ ํ˜•ํƒœ์ง€๋งŒ, ์™ธ๋ถ€์—์„œ ๋ณผ ๋•Œ๋Š” ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ๋„ฃ๋Š”๋‹ค๋Š” ์  ์™ธ์—๋Š” ์ž๋ฃŒํ˜•์ด ๊ฒฐ์ •๋œ ๋ณ€์ˆ˜๋ผ๋Š” ๊ฐœ๋…์œผ๋กœ ์ ‘๊ทผ

fun main() {
	  println(add(5, 6, 7))
    println(add2(5, 6, 7))
}

// ํ•จ์ˆ˜
fun add(a: Int, b: Int, c: Int): Int {
    return a + b + c
}

// ๋‹จ์ผํ‘œํ˜„์‹(๋ฐ˜ํ™˜ํ˜• ํƒ€์ž… ์ถ”๋ก )
fun add2(a: Int, b: Int, c: Int) = a + b + c

์กฐ๊ฑด๋ฌธ๊ณผ ๋น„๊ต์—ฐ์‚ฐ์ž

โœ… ์กฐ๊ฑด๋ฌธ

if

fun main() {
    var a = 11
    if (a < 10) {
        println("a is greater than 10")
    } else {
        println("a is less than or equal to 10")
    }
}

when

  • ๋“ฑํ˜ธ๋‚˜ ๋ถ€๋“ฑํ˜ธ๋Š” ์‚ฌ์šฉ ๋ถˆ๊ฐ€

fun doWhen (a: Any) {
    when(a) {
        1 -> println("this is number")
        "Hello" -> println("this is string")
        is Long -> println("this is long type")
        !is String -> println("this is not String type")
        else -> println("this is else area")
    }
}

fun doWhenReturn (a: Any) {
    var result = when(a) {
        1 -> "this is number"
        "Hello" -> "this is string"
        is Long -> "this is long type"
        !is String -> "this is not String type"
        else -> "this is else area"
    }
    println(result)
}

โœ… ๋น„๊ต์—ฐ์‚ฐ์ž

  • ๋ถ€๋“ฑํ˜ธ: <, โ‰ค, >, โ‰ฅ, โ‰ 

  • ๋“ฑํ˜ธ: ==

  • ์ž๋ฃŒํ˜• ํ™•์ธ: is, !is

    • ํ˜ธํ™˜ ์—ฌ๋ถ€๋ฅผ ์ฒดํฌํ•˜๊ณ  ํ˜•๋ณ€ํ™˜๊นŒ์ง€ ํ•œ๋ฒˆ์— ์ง„ํ–‰

    a is Int

๋ฐ˜๋ณต๋ฌธ

๋‹ค๋ฅธ ์–ธ์–ด์—์„œ์˜ ๋ฐ˜๋ณต๋ฌธ๊ณผ๋Š” ์•ฝ๊ฐ„์˜ ์ฐจ์ด๊ฐ€ ์žˆ๋‹ค.

fun main() {
	for (i in 0..9) {
        print(i)
    } // 0123456789
    
    println()
    for (i in 0..9 step 3) {
        print(i)
    } // 0369
    
    println()
    for (i in 9 downTo 0) {
        print(i)
    } // 9876543210
    
    println()
    for (i in 9 downTo 0 step 3) {
        print(i)
    } // 9630
    
    println()
    for (i in 'a'..'e') {
        print(i)
    } // abcde
}

๋ ˆ์ด๋ธ”์ด ๋‹ฌ๋ฆฐ ๋ฐ˜๋ณต๋ฌธ ๊ธฐ์ค€์œผ๋กœ ๋ฐ˜๋ณต๋ฌธ์„ ์ข…๋ฃŒ์‹œ์ผœ์ฃผ๋Š” ๊ธฐ๋Šฅ

  • ๋ ˆ์ด๋ธ” ์ด๋ฆ„๊ณผ @๊ธฐํ˜ธ๋กœ ์ฆ‰์‹œ ๋ฐ˜๋ณต๋ฌธ ์ข…๋ฃŒ

loop@for (i in 1..10) {
    for (j in 1..10) {
        if (i == 1 && j == 2) break@loop
        println("i : $i, j : $j")
    }
}

ํด๋ž˜์Šค

fun main() {
	var a = Person("๋ฐ•๋ณด์˜", 1990)
    var b = Person("์ „์ •๊ตญ", 1997)
    var c = Person("์žฅ์›์˜", 2004)
    
    println("์•ˆ๋…•ํ•˜์„ธ์š”. ${a.birthYear}๋…„์ƒ ${a.name}์ž…๋‹ˆ๋‹ค.")
    
    b.introduce()
    c.introduce()
    
    var d = Person("์ด๋ฃจ๋‹ค")
    var e = Person("์ฐจ์€์šฐ")
    var f = Person("๋ฅ˜์ˆ˜์ •")
}

class Person(var name:String, val birthYear:Int) { // ํด๋ž˜์Šค์˜ ์†์„ฑ๋“ค์„ ์„ ์–ธํ•จ๊ณผ ๋™์‹œ์— ์ƒ์„ฑ์ž๋ฅผ ์„ ์–ธํ•˜๋Š” ๋ฐฉ๋ฒ•
    /** init
     * ์ƒ์„ฑ์ž๋ฅผ ํ†ตํ•ด ์ธ์Šคํ„ด์Šค๊ฐ€ ๋งŒ๋“ค์–ด์งˆ ๋•Œ ํ˜ธ์ถœ๋˜๋Š” ํ•จ์ˆ˜
     */
    init {
        println("[init] ${this.birthYear}๋…„์ƒ ${this.name}๋‹˜์˜ ์ธ์Šคํ„ด์Šค๊ฐ€ ์ƒ์„ฑ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.")        
    }
    
    /** ๋ณด์กฐ ์ƒ์„ฑ์ž
     * ๋ณด์กฐ ์ƒ์„ฑ์ž๋ฅผ ๋งŒ๋“ค ๊ฒฝ์šฐ ๋ฐ˜๋“œ์‹œ ๊ธฐ๋ณธ ์ƒ์„ฑ์ž๋ฅผ ํ†ตํ•ด ์†์„ฑ์„ ์ดˆ๊ธฐํ™”
     */
    constructor(name:String) : this(name, 1997) {
        println("[constructor] ๋ณด์กฐ ์ƒ์„ฑ์ž๊ฐ€ ์‚ฌ์šฉ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.")
        
    }
    
    fun introduce() {
        println("[introduce] ์•ˆ๋…•ํ•˜์„ธ์š”. ${birthYear}๋…„์ƒ ${name}์ž…๋‹ˆ๋‹ค.")
    }
}
[init] 1990๋…„์ƒ ๋ฐ•๋ณด์˜๋‹˜์˜ ์ธ์Šคํ„ด์Šค๊ฐ€ ์ƒ์„ฑ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.
[init] 1997๋…„์ƒ ์ „์ •๊ตญ๋‹˜์˜ ์ธ์Šคํ„ด์Šค๊ฐ€ ์ƒ์„ฑ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.
[init] 2004๋…„์ƒ ์žฅ์›์˜๋‹˜์˜ ์ธ์Šคํ„ด์Šค๊ฐ€ ์ƒ์„ฑ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.
์•ˆ๋…•ํ•˜์„ธ์š”. 1990๋…„์ƒ ๋ฐ•๋ณด์˜์ž…๋‹ˆ๋‹ค.
[introduce] ์•ˆ๋…•ํ•˜์„ธ์š”. 1997๋…„์ƒ ์ „์ •๊ตญ์ž…๋‹ˆ๋‹ค.
[introduce] ์•ˆ๋…•ํ•˜์„ธ์š”. 2004๋…„์ƒ ์žฅ์›์˜์ž…๋‹ˆ๋‹ค.
[init] 1997๋…„์ƒ ์ด๋ฃจ๋‹ค๋‹˜์˜ ์ธ์Šคํ„ด์Šค๊ฐ€ ์ƒ์„ฑ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.
[constructor] ๋ณด์กฐ ์ƒ์„ฑ์ž๊ฐ€ ์‚ฌ์šฉ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.
[init] 1997๋…„์ƒ ์ฐจ์€์šฐ๋‹˜์˜ ์ธ์Šคํ„ด์Šค๊ฐ€ ์ƒ์„ฑ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.
[constructor] ๋ณด์กฐ ์ƒ์„ฑ์ž๊ฐ€ ์‚ฌ์šฉ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.
[init] 1997๋…„์ƒ ๋ฅ˜์ˆ˜์ •๋‹˜์˜ ์ธ์Šคํ„ด์Šค๊ฐ€ ์ƒ์„ฑ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.
[constructor] ๋ณด์กฐ ์ƒ์„ฑ์ž๊ฐ€ ์‚ฌ์šฉ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

์ƒ์†

fun main() {
	var a = Animal("๋ณ„์ด", 5, "๊ฐœ")
    var b = Dog("๋ณ„์ด", 5)
    
    a.introduce()
    b.introduce()
    
    b.bark()
    
    var c = Cat("๋ฃจ์ด", 1)
    
    c.introduce()
    c.meow()
}

/** open
 * ํด๋ž˜์Šค๊ฐ€ ์ƒ์†๋  ์ˆ˜ ์žˆ๋„๋ก ํ—ˆ์šฉํ•˜๋Š” ํ‚ค์›Œ๋“œ
 */
open class Animal (var name:String, var age:Int, var type:String) {
    fun introduce() {
        println("์ €๋Š” ${type} ${name}์ด๊ณ , ${age}์‚ด ์ž…๋‹ˆ๋‹ค.")
    }
}

/** ์ƒ์† ๊ทœ์น™
 * 1. ์„œ๋ธŒ ํด๋ž˜์Šค๋Š” ์ˆ˜ํผ ํด๋ž˜์Šค์— ์กด์žฌํ•˜๋Š” ์†์„ฑ๊ณผ ๊ฐ™์€ ์ด๋ฆ„์˜ ์†์„ฑ์„ ๊ฐ€์งˆ ์ˆ˜ ์—†๋‹ค.
 * 2. ์„œ๋ธŒ ํด๋ž˜์Šค๊ฐ€ ์ƒ์„ฑ๋  ๋•Œ ๋ฐ˜๋“œ์‹œ ์ˆ˜ํผํด๋ž˜์Šค์˜ ์ƒ์„ฑ์ž๊นŒ์ง€ ํ˜ธ์ถœ๋˜์–ด์•ผ ํ•œ๋‹ค.
 */
 class Dog (name:String, age:Int) : Animal (name, age, "๊ฐœ") {
     fun bark() {
         println("๋ฉ๋ฉ")
     }
 }
 
class Cat (name:String, age:Int) : Animal (name, age, "๊ณ ์–‘์ด") {
     fun meow() {
         println("์•ผ์˜น์•ผ์˜น")
     }
}

์˜ค๋ฒ„๋ผ์ด๋”ฉ

fun main() {
    var t = Tiger()
    t.eat()
}

/** 
 * ์ƒ์†์ด ๊ฐ€๋Šฅํ•˜๋„๋ก open ๋œ ํด๋ž˜์Šค
 */
open class Animal () {
    // ์ˆ˜ํผ ํด๋ž˜์Šค์—์„œ open ๋œ ํ•จ์ˆ˜๋Š” ์„œ๋ธŒ ํด๋ž˜์Šค์—์„œ override ๊ฐ€๋Šฅ
    open fun eat() {
        println("์Œ์‹์„ ๋จน์Šต๋‹ˆ๋‹ค")
    }
}

class Tiger : Animal() {
    override fun eat() {
        println("๊ณ ๊ธฐ๋ฅผ ๋จน์Šต๋‹ˆ๋‹ค")
    }
}

์ถ”์ƒํ™”

์ถ”์ƒ ํด๋ž˜์Šค: ์ถ”์ƒ ํ•จ์ˆ˜๋ฅผ ํฌํ•จํ•˜๋Š” ํด๋ž˜์Šค

fun main() {
    var r = Rabbit()
    r.eat()
    r.sniff()
}

// ์ถ”์ƒ ํด๋ž˜์Šค
abstract class Animal () {
    abstract fun eat() // ์ถ”์ƒ ํ•จ์ˆ˜
    fun sniff() {
        println("ํ‚ํ‚")
    }
}

class Rabbit : Animal() {
    override fun eat() {
        println("๋‹น๊ทผ์„ ๋จน์Šต๋‹ˆ๋‹ค")
    }
}

์ธํ„ฐํŽ˜์ด์Šค: ์†์„ฑ, ์ถ”์ƒํ•จ์ˆ˜, ์ผ๋ฐ˜ํ•จ์ˆ˜ ํฌํ•จ

  • ๊ตฌํ˜„๋ถ€๊ฐ€ ์žˆ๋Š” ํ•จ์ˆ˜ โ†’ open ํ•จ์ˆ˜๋กœ ๊ฐ„์ฃผ

  • ๊ตฌํ˜„๋ถ€๊ฐ€ ์—†๋Š” ํ•จ์ˆ˜ โ†’ abstract ํ•จ์ˆ˜๋กœ ๊ฐ„์ฃผ

  • โš ๏ธ ์—ฌ๋Ÿฌ๊ฐœ์˜ ์ธํ„ฐํŽ˜์ด์Šค๋‚˜ ํด๋ž˜์Šค์—์„œ ๊ฐ™์€ ์ด๋ฆ„๊ณผ ํ˜•ํƒœ๋ฅผ ๊ฐ€์ง„ ํ•จ์ˆ˜๋ฅผ ๊ตฌํ˜„ํ•˜๊ณ  ์žˆ๋‹ค๋ฉด,

    • ์„œ๋ธŒํด๋ž˜์Šค์—์„œ๋Š” ํ˜ผ์„ ์ด ์ผ์–ด๋‚˜์ง€ ์•Š๋„๋ก ๋ฐ˜๋“œ์‹œ ์˜ค๋ฒ„๋ผ์ด๋”ฉํ•˜์—ฌ ์žฌ๊ตฌํ˜„ ํ•„์š”

fun main() {
    var d = Dog()
    
    d.run()
    d.eat()
}

interface Runner {
    fun run()
}

interface Eater {
    fun eat() {
        println("์Œ์‹์„ ๋จน์Šต๋‹ˆ๋‹ค")
    }
}

class Dog : Runner, Eater {
    override fun run() {
        println("์šฐ๋‹ค๋‹ค๋‹ค ๋œ๋‹ˆ๋‹ค")
    }
    
    override fun eat() {
        println("ํ—ˆ๊ฒ์ง€๊ฒ ๋จน์Šต๋‹ˆ๋‹ค")
    }
}

ํ”„๋กœ์ ํŠธ ๊ตฌ์กฐ

  • Project

    • ๋ชจ๋“  ๋‚ด์šฉ์„ ๋‹ด๋Š” ํฐ ํ‹€

  • Module

    • ํ•˜๋‚˜์˜ ํ”„๋กœ์ ํŠธ๋Š” ์—ฌ๋Ÿฌ๊ฐœ์˜ ๋ชจ๋“ˆ๋กœ ์ด๋ฃจ์–ด์งˆ ์ˆ˜ ์žˆ๋‹ค.

      • ๋ชจ๋“ˆ์€ ์ง์ ‘ ๋งŒ๋“ค ์ˆ˜๋„ ์žˆ๊ณ , ํ•„์š”ํ•œ ๊ธฐ๋Šฅ์„ ๋ฏธ๋ฆฌ ๊ตฌํ˜„ํ•ด ๋‘” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ๋ชจ๋“ˆ์„ ๊ฐ€์ ธ์™€ ์‚ฌ์šฉ ๊ฐ€๋Šฅ

    • ๋ชจ๋“ˆ ์•ˆ์—๋Š” ๋‹ค์ˆ˜์˜ ํด๋”(kt, ๋ชจ๋“ˆ ๊ด€๋ จ ์„ค์ •, ๋ฆฌ์†Œ์Šค ํŒŒ์ผ ๋“ฑ)์™€ ํŒŒ์ผ์ด ์กด์žฌ

  • Package

    • ์†Œ์Šค ์ฝ”๋“œ์˜ ์†Œ์†์„ ์ง€์ •ํ•˜๊ธฐ ์œ„ํ•œ ๋…ผ๋ฆฌ์  ๋‹จ์œ„

    • ์ผ๋ฐ˜์ ์œผ๋กœ ํŒจํ‚ค์ง€ ์ด๋ฆ„์„ ์ง€์„ ๋•Œ ํšŒ์‚ฌ ๋„๋ฉ”์ธ์„ ๊ฑฐ๊พธ๋กœ ํ•ด์„œ ํ”„๋กœ์ ํŠธ๋ช…๊ณผ ์„ธ๋ถ€ ๊ธฐ๋Šฅ์„ ๋ถ™์ด๋Š” ๋ฐฉ์‹

      • com.youtube.aaron

      • com.youtube.aaron.base

      • com.youtube.aaron.kotlin

      • com.youtube.aaron.talk

    • ์ฝ”ํ‹€๋ฆฐ์€ ์ž๋ฐ”์™€ ๋‹ฌ๋ฆฌ ํด๋” ๊ตฌ์กฐ์™€ ํŒจํ‚ค์ง€ ๋ช…์„ ์ผ์น˜์‹œํ‚ค์ง€ ์•Š์•„๋„ ๋œ๋‹ค.

      • ๋‹จ์ˆœํžˆ ํŒŒ์ผ ์ƒ๋‹จ์— ํŒจํ‚ค์ง€๋งŒ ๋ช…์‹œํ•ด ์ฃผ๋ฉด ์ปดํŒŒ์ผ๋Ÿฌ๊ฐ€ ์•Œ์•„์„œ ์ฒ˜๋ฆฌ

์ฝ”ํ‹€๋ฆฐ์€ ํด๋ž˜์Šค๋ช…๊ณผ ํŒŒ์ผ๋ช…์ด ์ผ์น˜ํ•˜์ง€ ์•Š์•„๋„ ๋˜๋ฉฐ,

ํ•˜๋‚˜์˜ ํŒŒ์ผ์— ์—ฌ๋Ÿฌ๊ฐœ์˜ ํด๋ž˜์Šค๋ฅผ ๋„ฃ์–ด๋„ ์•Œ์•„์„œ ์ปดํŒŒ์ผ ๊ฐ€๋Šฅ

  • ํŒŒ์ผ์ด๋‚˜ ํด๋” ๊ธฐ์ค€์œผ๋กœ ๊ตฌ๋ถ„ํ•˜์ง€ ์•Š๊ณ  ํŒŒ์ผ๋‚ด์— ์žˆ๋Š” package ํ‚ค์›Œ๋“œ ๊ธฐ์ค€์œผ๋กœ ๊ตฌ๋ถ„


์Šค์ฝ”ํ”„์™€ ์ ‘๊ทผ ์ œํ•œ์ž

โœ… ์Šค์ฝ”ํ”„

ํŒจํ‚ค์ง€ ์•ˆ์— ๋ณ€์ˆ˜, ํ•จ์ˆ˜, ํด๋ž˜์Šค๋Š” ๋ชจ๋‘ ํ•˜๋‚˜์˜ ์Šค์ฝ”ํ”„์— ์žˆ๋Š” ๋ฉค๋ฒ„

ํ•จ์ˆ˜, ํด๋ž˜์Šค์•ˆ์— ๋˜๋‹ค๋ฅธ ๋ณ€์ˆ˜, ํ•จ์ˆ˜๊ฐ€ ์กด์žฌํ•œ๋‹ค๋ฉด ํŒจํ‚ค์ง€ ์•ˆ์— ๋˜๋‹ค๋ฅธ ํ•˜์œ„ ์Šค์ฝ”ํ”„๋กœ ๋™์ž‘

์Šค์ฝ”ํ”„์— ๋Œ€ํ•œ ์„ธ ๊ฐ€์ง€ ๊ทœ์น™

  • (1) ์Šค์ฝ”ํ”„ ์™ธ๋ถ€์—์„œ๋Š” ์Šค์ฝ”ํ”„ ๋‚ด๋ถ€์˜ ๋ฉค๋ฒ„๋ฅผ ์ฐธ์กฐ์—ฐ์‚ฐ์ž๋กœ๋งŒ ์ฐธ์กฐ ๊ฐ€๋Šฅ

a.eat()
import com.google.aaron
import com.google.aaron.A
  • (2) ๋™์ผ ์Šค์ฝ”ํ”„ ๋‚ด์—์„œ๋Š” ๋ฉค๋ฒ„๋“ค์„ ๊ณต์œ ํ•  ์ˆ˜ ์žˆ์Œ

  • (3) ํ•˜์œ„ ์Šค์ฝ”ํ”„์—์„œ๋Š” ์ƒ์œ„ ์Šค์ฝ”ํ”„์˜ ๋ฉค๋ฒ„๋ฅผ ์žฌ์ •์˜ ๊ฐ€๋Šฅ

โœ… ์ ‘๊ทผ ์ œํ•œ์ž

๋ณ€์ˆ˜, ํ•จ์ˆ˜, ํด๋ž˜์Šค ์„ ์–ธ ์‹œ ๋งจ ์•ž์— ๋ถ™์—ฌ ์‚ฌ์šฉ

์Šค์ฝ”ํ”„ ์™ธ๋ถ€์—์„œ ์Šค์ฝ”ํ”„ ๋‚ด๋ถ€์— ์ ‘๊ทผํ•  ๋•Œ ๊ทธ ๊ถŒํ•œ์„ ๊ฐœ๋ฐœ์ž๊ฐ€ ์ œ์–ดํ•  ์ˆ˜ ์žˆ๋Š” ๊ธฐ๋Šฅ

  • public

  • internal

  • private

  • protected

Package Scope

public (default)
์–ด๋–ค ํŒจํ‚ค์ง€์—์„œ๋„ ์ ‘๊ทผ ๊ฐ€๋Šฅ

internal

๊ฐ™์€ ๋ชจ๋“ˆ ๋‚ด์—์„œ๋งŒ ์ ‘๊ทผ ๊ฐ€๋Šฅ

private

๊ฐ™์€ ํŒŒ์ผ ๋‚ด์—์„œ๋งŒ ์ ‘๊ทผ ๊ฐ€๋Šฅ

protected

๋ฏธ์‚ฌ์šฉ

Class Scope

public (default)
ํด๋ž˜์Šค ์™ธ๋ถ€์—์„œ ํ•ญ์ƒ ์ ‘๊ทผ ๊ฐ€๋Šฅ

private

ํด๋ž˜์Šค ๋‚ด๋ถ€์—์„œ๋งŒ ์ ‘๊ทผ ๊ฐ€๋Šฅ

protected

ํด๋ž˜์Šค ์ž์‹ ๊ณผ ์ƒ์†๋ฐ›์€ ํด๋ž˜์Šค์—์„œ ์ ‘๊ทผ ๊ฐ€๋Šฅ

internal

๋ฏธ์‚ฌ์šฉ


๊ณ ์ฐจํ•จ์ˆ˜์™€ ๋žŒ๋‹คํ•จ์ˆ˜

โœ… ๊ณ ์ฐจํ•จ์ˆ˜

ํ•จ์ˆ˜๋ฅผ ๋งˆ์น˜ ํด๋ž˜์Šค์—์„œ ๋งŒ๋“ค์–ด๋‚ธ ์ธ์Šคํ„ด์Šค์ฒ˜๋Ÿผ ์ทจ๊ธ‰ํ•˜๋Š” ๋ฐฉ๋ฒ•

  • ํ•จ์ˆ˜๋ฅผ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ๋„˜๊ฒจ์ค„ ์ˆ˜๋„ ์žˆ๊ณ , ๊ฒฐ๊ณผ๊ฐ’์œผ๋กœ ๋ฐ˜ํ™˜๋ฐ›์„ ์ˆ˜๋„ ์žˆ๋Š” ๋ฐฉ๋ฒ•

์ฝ”ํ‹€๋ฆฐ์—์„œ๋Š” ๋ชจ๋“  ํ•จ์ˆ˜๋ฅผ ๊ณ ์ฐจํ•จ์ˆ˜๋กœ ์‚ฌ์šฉ ๊ฐ€๋Šฅ

  • :: โ†’ ์ผ๋ฐ˜ ํ•จ์ˆ˜๋ฅผ ๊ณ ์ฐจ ํ•จ์ˆ˜๋กœ ๋ณ€๊ฒฝํ•ด ์ฃผ๋Š” ์—ฐ์‚ฐ์ž

  • ํ•จ์ˆ˜๋ฅผ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ๋ฐ›์„ ๊ฒฝ์šฐ ํƒ€์ž…์€ ํ•จ์ˆ˜์˜ (ํŒŒ๋ผ๋ฏธํ„ฐ ์ž๋ฃŒํ˜•) -> ๋ฐ˜ํ™˜ํ˜• ์ž๋ฃŒํ˜•

fun main() {
    b(::a) // ์ผ๋ฐ˜ ํ•จ์ˆ˜๋ฅผ ๊ณ ์ฐจ ํ•จ์ˆ˜๋กœ ๋ณ€๊ฒฝ
}

fun a (str: String) {
    println("$str ํ•จ์ˆ˜ a")
}

// ํ•จ์ˆ˜๋ฅผ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ๋ฐ›๊ธฐ. (ํŒŒ๋ผ๋ฏธํ„ฐ ์ž๋ฃŒํ˜•) -> ๋ฐ˜ํ™˜ํ˜• ์ž๋ฃŒํ˜•
fun b (function: (String)->Unit) {
    function("b๊ฐ€ ํ˜ธ์ถœํ•œ")
}

โœ… ๋žŒ๋‹คํ•จ์ˆ˜

๋žŒ๋‹คํ•จ์ˆ˜๋Š” ์ผ๋ฐ˜ํ•จ์ˆ˜์™€ ๋‹ฌ๋ฆฌ ๊ทธ ์ž์ฒด๊ฐ€ ๊ณ ์ฐจํ•จ์ˆ˜์ด๋ฏ€๋กœ ๋ณ„๋„์˜ ์—ฐ์‚ฐ์ž ์—†์ด ๋ณ€์ˆ˜์— ๋‹ด์„ ์ˆ˜ ์žˆ๋‹ค.

fun main() {
    /** ์ž๋ฃŒํ˜• ์ž๋™ ์ถ”๋ก ์œผ๋กœ ์ถ•์•ฝ ์‚ฌ์šฉ
     * var c: (String) -> Unit = { str:String -> println("$str ํ•จ์ˆ˜ a")}
     * var c: (String) -> Unit = { str -> println("$str ํ•จ์ˆ˜ a")}
     */
    var c = { str:String -> println("$str ํ•จ์ˆ˜ a")}
    b(c)
}

fun b (function: (String)->Unit) {
    function("b๊ฐ€ ํ˜ธ์ถœํ•œ")
}

โ„น๏ธ ๊ณ ์ฐจํ•จ์ˆ˜์™€ ๋žŒ๋‹คํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํ•จ์ˆ˜๋ฅผ ์ผ์ข…์˜ ๋ณ€์ˆ˜๋กœ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ํŽธ์˜์„ฑ

  • ์ปฌ๋ ‰์…˜ ์กฐ์ž‘์ด๋‚˜ ์Šค์ฝ”ํ”„ ํ•จ์ˆ˜์—๋„ ๋„์›€


์Šค์ฝ”ํ”„ ํ•จ์ˆ˜

ํ•จ์ˆ˜ํ˜• ์–ธ์–ด์˜ ํŠน์ง•์„ ๋” ํŽธ๋ฆฌํ•˜๊ฒŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ๊ธฐ๋ณธ ์ œ๊ณตํ•˜๋Š” ํ•จ์ˆ˜๋“ค

ํด๋ž˜์Šค์—์„œ ์ƒ์„ฑํ•œ ์ธ์Šคํ„ด์Šค๋ฅผ ์Šค์ฝ”ํ”„ ํ•จ์ˆ˜์— ์ „๋‹ฌํ•˜๋ฉด,

  • ์ธ์Šคํ„ด์Šค์˜ ์†์„ฑ์ด๋‚˜ ํ•จ์ˆ˜๋ฅผ ์ข€ ๋” ๊น”๋”ํ•˜๊ฒŒ ๋ถˆ๋Ÿฌ ์“ธ ์ˆ˜ ์žˆ๋‹ค.

๐Ÿ“ฆ apply

์ธ์Šคํ„ด์Šค ์ƒ์„ฑ ํ›„ ๋ณ€์ˆ˜์— ๋‹ด๊ธฐ ์ „ ์ดˆ๊ธฐํ™” ๊ณผ์ •์„ ์ˆ˜ํ–‰ํ•  ๋•Œ ์ฃผ๋กœ ์‚ฌ์šฉ

  • apply์˜ scope ์•ˆ์—์„œ ์ง์ ‘ ์ธ์Šคํ„ด์Šค์˜ ์†์„ฑ๊ณผ ํ•จ์ˆ˜๋ฅผ ์ฐธ์กฐ์—ฐ์‚ฐ์ž ์—†์ด ์‚ฌ์šฉ ๊ฐ€๋Šฅ

    • ๋˜ํ•œ ์ธ์Šคํ„ด์Šค ์ž์‹ ์„ ๋‹ค์‹œ ๋ฐ˜ํ™˜ํ•˜๋ฏ€๋กœ ์ƒ์„ฑ๋˜์ž๋งˆ์ž ์กฐ์ž‘๋œ ์ธ์Šคํ„ด์Šค๋ฅผ ๋ณ€์ˆ˜์— ๋ฐ”๋กœ ์ดˆ๊ธฐํ™” ๊ฐ€๋Šฅ

fun main() {
    var a = Book("์ฝ”ํ‹€๋ฆฐ ๊ฐ•์˜", 10000).apply {
        name = "[์ดˆํŠน๊ฐ€] " + name
        discount()
    }
    println(a.name + ", " + a.price) // [์ดˆํŠน๊ฐ€] ์ฝ”ํ‹€๋ฆฐ ๊ฐ•์˜, 8000
}

class Book(var name: String, var price: Int) {
    fun discount() {
        price -= 2000
    }
}

๐Ÿ“ฆ run

์ธ์Šคํ„ด์Šค๊ฐ€ ๋งŒ๋“ค์–ด์ง„ ํ›„์— ์ธ์Šคํ„ด์Šค์˜ ํ•จ์ˆ˜๋‚˜ ์†์„ฑ์„ ์Šค์ฝ”ํ”„ ๋‚ด์—์„œ ์‚ฌ์šฉํ•ด์•ผ ํ•  ๊ฒฝ์šฐ ์œ ์šฉ

  • apply์™€ ๋™์ผํ•˜๊ฒŒ ์Šค์ฝ”ํ”„ ์•ˆ์—์„œ ์ฐธ์กฐ์—ฐ์‚ฐ์ž๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š์•„๋„ ๋œ๋‹ค๋Š” ์ ์€ ๊ฐ™์ง€๋งŒ, ์ผ๋ฐ˜ ๋žŒ๋‹คํ•จ์ˆ˜์ฒ˜๋Ÿผ ์ธ์Šคํ„ด์Šค๋Œ€์‹  ๊ฒฐ๊ณผ๊ฐ’์„ ๋ฐ˜ํ™˜

fun main() {
    var a = Book("์ฝ”ํ‹€๋ฆฐ ๊ฐ•์˜", 10000).apply {
        name = "[์ดˆํŠน๊ฐ€] " + name
        discount()
    }
    println(a.name + ", " + a.price) // [์ดˆํŠน๊ฐ€] ์ฝ”ํ‹€๋ฆฐ ๊ฐ•์˜, 8000
    
    a.run {
        println("์ƒํ’ˆ๋ช…: ${name}, ๊ฐ€๊ฒฉ: ${price}์›") // ์ƒํ’ˆ๋ช…: [์ดˆํŠน๊ฐ€] ์ฝ”ํ‹€๋ฆฐ ๊ฐ•์˜, ๊ฐ€๊ฒฉ: 8000์›
    }
}

๐Ÿ“ฆ with

  • run๊ณผ ๋™์ผํ•œ ๊ธฐ๋Šฅ์„ ๊ฐ€์ง€์ง€๋งŒ, ์ธ์Šคํ„ด์Šค๋ฅผ ์ฐธ์กฐ์—ฐ์‚ฐ์ž ๋Œ€์‹  ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ๋ฐ›๋Š”๋‹ค๋Š” ์ฐจ์ด

a.run { ... }
with(a) { ... }

...

fun main() {
    
    var a = Book("์ฝ”ํ‹€๋ฆฐ ๊ฐ•์˜", 10000).apply {
        name = "[์ดˆํŠน๊ฐ€] " + name
        discount()
    }

    with(a) {
        println("์ƒํ’ˆ๋ช…: ${name}, ๊ฐ€๊ฒฉ: ${price}์›") // ์ƒํ’ˆ๋ช…: [์ดˆํŠน๊ฐ€] ์ฝ”ํ‹€๋ฆฐ ๊ฐ•์˜, ๊ฐ€๊ฒฉ: 8000์›
    }
}

๐Ÿ“ฆ also

  • apply์™€ ์œ ์‚ฌํ•˜๊ฒŒ ์ฒ˜๋ฆฌ๊ฐ€ ๋๋‚˜๋ฉด ์ธ์Šคํ„ด์Šค๋ฅผ ๋ฐ˜ํ™˜ํ•˜์ง€๋งŒ, ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ์ธ์Šคํ„ด์Šค๋ฅผ ๋„˜๊ธด ๊ฒƒ๊ณผ ๊ฐ™์ด it์„ ํ†ตํ•ด ์ธ์Šคํ„ด์Šค๋ฅผ ์‚ฌ์šฉ

    • ๊ฐ™์€ ์ด๋ฆ„์˜ ๋ณ€์ˆ˜๋‚˜ ํ•จ์ˆ˜๊ฐ€ ์Šค์ฝ”ํ”„ ๋ฐ”๊นฅ์— ์ค‘๋ณต๋˜์–ด ์žˆ๋Š” ๊ฒฝ์šฐ ํ˜ผ๋ž€์„ ๋ฐฉ์ง€ํ•˜๊ธฐ ์œ„ํ•จ

fun main() {

    var a = Book("์ฝ”ํ‹€๋ฆฐ ๊ฐ•์˜", 10000).also {
        it.name = "[์ดˆํŠน๊ฐ€] " + it.name
        it.discount()
    }

    a.run {
        println("์ƒํ’ˆ๋ช…: ${name}, ๊ฐ€๊ฒฉ: ${price}์›") // ์ƒํ’ˆ๋ช…: [์ดˆํŠน๊ฐ€] ์ฝ”ํ‹€๋ฆฐ ๊ฐ•์˜, ๊ฐ€๊ฒฉ: 8000์›
    }
}

๐Ÿ“ฆ let

  • run๊ณผ ์œ ์‚ฌํ•˜๊ฒŒ ์ฒ˜๋ฆฌ๊ฐ€ ๋๋‚˜๋ฉด ์ตœ์ข…๊ฐ’์„ ๋ฐ˜ํ™˜ํ•˜์ง€๋งŒ, ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ์ธ์Šคํ„ด์Šค๋ฅผ ๋„˜๊ธด ๊ฒƒ๊ณผ ๊ฐ™์ด it์„ ํ†ตํ•ด ์ธ์Šคํ„ด์Šค๋ฅผ ์‚ฌ์šฉ

    • ๊ฐ™์€ ์ด๋ฆ„์˜ ๋ณ€์ˆ˜๋‚˜ ํ•จ์ˆ˜๊ฐ€ ์Šค์ฝ”ํ”„ ๋ฐ”๊นฅ์— ์ค‘๋ณต๋˜์–ด ์žˆ๋Š” ๊ฒฝ์šฐ ํ˜ผ๋ž€์„ ๋ฐฉ์ง€ํ•˜๊ธฐ ์œ„ํ•จ

fun main() {
    
    var price = 5000
    
    var a = Book("์ฝ”ํ‹€๋ฆฐ ๊ฐ•์˜", 10000).apply {
        name = "[์ดˆํŠน๊ฐ€] " + name
        discount()
    }

    a.run {
        // main ํ•จ์ˆ˜์˜ price ๋ณ€์ˆ˜๋ฅผ ์šฐ์„ 
        println("์ƒํ’ˆ๋ช…: ${name}, ๊ฐ€๊ฒฉ: ${price}์›") // ์ƒํ’ˆ๋ช…: [์ดˆํŠน๊ฐ€] ์ฝ”ํ‹€๋ฆฐ ๊ฐ•์˜, ๊ฐ€๊ฒฉ: 5000์›
    }
    
    a.let {
        println("์ƒํ’ˆ๋ช…: ${it.name}, ๊ฐ€๊ฒฉ: ${it.price}์›") // ์ƒํ’ˆ๋ช…: [์ดˆํŠน๊ฐ€] ์ฝ”ํ‹€๋ฆฐ ๊ฐ•์˜, ๊ฐ€๊ฒฉ: 8000์›
    }
}

Object

๋‹จ ํ•˜๋‚˜์˜ ๊ฐ์ฒด๋งŒ์œผ๋กœ ๊ณตํ†ต์ ์ธ ์†์„ฑ๊ณผ ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ

์ƒ์„ฑ์ž ์—†์ด ๊ฐ์ฒด๋ฅผ ์ง์ ‘ ์ƒ์„ฑ

object๋กœ ์„ ์–ธ๋œ ๊ฐ์ฒด๋Š” ์ตœ์ดˆ ์‚ฌ์šฉ ์‹œ ์ž๋™์œผ๋กœ ์ƒ์„ฑ๋˜๊ณ , ์ดํ›„์—๋Š” ์ฝ”๋“œ ์ „์ฒด์—์„œ ๊ณต์šฉ์œผ๋กœ ์‚ฌ์šฉ๋  ์ˆ˜ ์žˆ๋‹ค.

fun main() {
    // ์ธ์Šคํ„ด์Šค๋ฅผ ์ƒ์„ฑํ•˜์ง€ ์•Š๊ณ  ๊ทธ ์ž์ฒด๋กœ ๊ฐ์ฒด
	  println(Counter.count)
    
    Counter.countUp()
    Counter.countUp()
    
    println(Counter.count)
    
    Counter.clear()
    
  	println(Counter.count)
}

object Counter {
    var count = 0
    
    fun countUp() {
        count++
    }
    
    fun clear() {
        count = 0
    }
}

Companion Object

  • ๊ธฐ์กด ํด๋ž˜์Šค ์•ˆ์— ์žˆ๋Š” ์˜ค๋ธŒ์ ํŠธ(static ๋ฉค๋ฒ„์™€ ์œ ์‚ฌ)

fun main() {
    var a = FoodPoll("์งœ์žฅ")
    var b = FoodPoll("์งฌ๋ฝ•")
    
    a.vote()
    a.vote()

    b.vote()
    b.vote()
    b.vote()
    
    println("${a.name} : ${a.count}")  // ์งœ์žฅ : 2
    println("${b.name} : ${b.count}") // ์งฌ๋ฝ• : 3
    println("์ด๊ณ„ : ${FoodPoll.total}") // ์ด๊ณ„ : 5
}

class FoodPoll (val name: String) {
    companion object {
        var total = 0 // ๋‹ค๋ฅธ ์ธ์Šคํ„ด์Šค์—์„œ ๊ณต์œ ํ•˜๋Š” ์ž์›
    }
    
    var count = 0
    
    fun vote() {
        total++
        count++
    }
}

์ต๋ช…๊ฐ์ฒด์™€ ์˜ต์ €๋ฒ„ ํŒจํ„ด

์ด๋ฒคํŠธ๊ฐ€ ๋ฐœ์ƒํ•  ๋•Œ๋งˆ๋‹ค ์ฆ‰๊ฐ์ ์œผ๋กœ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋„๋ก ๋งŒ๋“œ๋Š” ํŒจํ„ด

์ด๋ฒคํŠธ๋ฅผ ์ˆ˜์‹ ํ•˜๋Š” ํด๋ž˜์Šค์™€ ์ด๋ฒคํŠธ์˜ ๋ฐœ์ƒ ๋ฐ ์ „๋‹ฌ์„ ๋‹ด๋‹นํ•˜๋Š” ํด๋ž˜์Šค์™€ ํ†ต์‹ ์„ ์œ„ํ•ด ์‚ฌ์šฉ๋˜๋Š” ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ Observer, ์ฝ”ํ‹€๋ฆฐ์—์„œ๋Š” listener ๋ผ๊ณ  ๋ถ€๋ฅธ๋‹ค.

  • ์ด๋ฒคํŠธ๋ฅผ ๋„˜๊ฒจ์ฃผ๋Š” ํ–‰์œ„๋Š” callback

fun main() {
    EventPrinter().start()
}

interface EventListener {
    fun onEvent(count: Int)
}

class Counter(var listener: EventListener) {
    fun count() {
        for (i in 1..100) {
            if (i % 5 == 0) listener.onEvent(i)
        }
    }
}

class EventPrinter: EventListener {
    override fun onEvent(count: Int) {
        print("${count}-")
    }
    
    fun start() {
        val counter = Counter(this)
        counter.count()
    }
}

์ต๋ช…ํด๋ž˜์Šค ํ™œ์šฉ

  • object์™€ ํ˜•ํƒœ๋Š” ๋น„์Šทํ•˜์ง€๋งŒ ์ด๋ฆ„์ด ์—†๋‹ค๋Š” ์ฐจ์ด

EventPrinter().start()

...

class EventPrinter {
    fun start() {
        val counter = Counter(object: EventListener {
            override fun onEvent(count: Int) {
                print("${count}-")
            }
        })
        counter.count()
    }
}

ํด๋ž˜์Šค์˜ ๋‹คํ˜•์„ฑ

Up-casting: ์ƒ์œ„ ์ž๋ฃŒํ˜•์ธ ์ˆ˜ํผํด๋ž˜์Šค๋ฅผ ๋ณ€ํ™˜

var a: Drink = Cola()

Down-casting: Up-casting๋œ ์ธ์Šคํ„ด์Šค๋ฅผ ๋‹ค์‹œ ํ•˜์œ„ ์ž๋ฃŒํ˜•์œผ๋กœ ๋ณ€ํ™˜

  • as: ๋ณ€์ˆ˜๋ฅผ ํ˜ธํ™˜๋˜๋Š” ์ž๋ฃŒํ˜•์œผ๋กœ ๋ณ€ํ™˜ํ•ด์ฃผ๋Š” ์บ์ŠคํŒ… ์—ฐ์‚ฐ์ž

    • ๋ฐ˜ํ™˜๊ฐ’๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ๋ณ€์ˆ˜ ์ž์ฒด๋„ ๋‹ค์šด์บ์ŠคํŒ…

var a: Drink = Cola()

a as Cola // ์ดํ›„ a๋Š” Cola๋กœ ๋™์ž‘
var b = a as Cola // ๋ณ€ํ™˜ ๊ฒฐ๊ณผ๋ฅผ ๋ฐ˜ํ™˜๋ฐ›์•„ ๋ณ€์ˆ˜์— ์ดˆ๊ธฐํ™”
  • is: ๋ณ€์ˆ˜๊ฐ€ ์ž๋ฃŒํ˜•์— ํ˜ธํ™˜๋˜๋Š”์ง€ ์ฒดํฌํ•œ ํ›„ ๋ณ€ํ™˜ํ•ด์ฃผ๋Š” ์บ์ŠคํŒ… ์—ฐ์‚ฐ์ž (์กฐ๊ฑด๋ฌธ ๋‚ด์—์„œ ์‚ฌ์šฉ)

var a: Drink = Cola()
if (a is Cola) {
    // ํ•ด๋‹น ์˜์—ญ ์•ˆ์—์„œ๋งŒ a๊ฐ€ Cola๋กœ ์‚ฌ์šฉ
}

Example

fun main() {
	var a = Drink()
    a.drink() // ์Œ๋ฃŒ๋ฅผ ๋งˆ์‹ญ๋‹ˆ๋‹ค.
    
    var b: Drink = Cola()
    b.drink() // ์Œ๋ฃŒ์ค‘์— ์ฝœ๋ผ๋ฅผ ๋งˆ์‹ญ๋‹ˆ๋‹ค.
    
    if (b is Cola) {
        b.washDished() // ์ฝœ๋ผ๋กœ ์„ค๊ฑฐ์ง€๋ฅผ ํ•ฉ๋‹ˆ๋‹ค.
    }
    
    var c = b as Cola
    c.washDished() // ์ฝœ๋ผ๋กœ ์„ค๊ฑฐ์ง€๋ฅผ ํ•ฉ๋‹ˆ๋‹ค.
    b.washDished()  // ๋ฐ˜ํ™˜๊ฐ’๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ๋ณ€์ˆ˜ ์ž์ฒด๋„ ๋‹ค์šด์บ์ŠคํŒ…
}

open class Drink {
    var name = "์Œ๋ฃŒ"
    
    open fun drink() {
        println("${name}๋ฅผ ๋งˆ์‹ญ๋‹ˆ๋‹ค.")
    }
}

class Cola: Drink() {
    var type = "์ฝœ๋ผ"
    
    override fun drink() {
        println("${name}์ค‘์— ${type}๋ฅผ ๋งˆ์‹ญ๋‹ˆ๋‹ค.")
    }
    
    fun washDished() {
        println("${type}๋กœ ์„ค๊ฑฐ์ง€๋ฅผ ํ•ฉ๋‹ˆ๋‹ค.")
    }
}

์ œ๋„ˆ๋ฆญ

ํด๋ž˜์Šค๋‚˜ ํ•จ์ˆ˜์—์„œ ์‚ฌ์šฉํ•˜๋Š” ์ž๋ฃŒํ˜•์„ ์™ธ๋ถ€์—์„œ ์ง€์ •ํ•  ์ˆ˜ ์žˆ๋Š” ๊ธฐ๋Šฅ

ํ•จ์ˆ˜๋‚˜ ํด๋ž˜์Šค๋ฅผ ์„ ์–ธํ•  ๋•Œ ๊ณ ์ •์ ์ธ ์ž๋ฃŒํ˜• ๋Œ€์‹  ์‹ค์ œ ์ž๋ฃŒํ˜•์œผ๋กœ ๋Œ€์ฒด๋˜๋Š” ํƒ€์ž… ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ๋ฐ›์•„ ์‚ฌ์šฉ

  • ์ œ๋„ค๋ฆญ์„ ์‚ฌ์šฉํ•  ๊ฒฝ์šฐ ์ž๋ฃŒํ˜•์„ ๋Œ€์ฒดํ•˜๊ฒŒ ๋˜์–ด ์บ์ŠคํŒ…์„ ๋ฐฉ์ง€ํ•  ์ˆ˜ ์žˆ๊ณ , ์„ฑ๋Šฅ์„ ๋†’์ผ ์ˆ˜ ์žˆ๋‹ค.

ํด๋ž˜์Šค์— ์ ์šฉ

fun main() {
	UsingGeneric(A()).doShouting()
    UsingGeneric(B()).doShouting()
    UsingGeneric(C()).doShouting()
}

open class A {
    open fun shout() {
        println("A๊ฐ€ ์†Œ๋ฆฌ์นฉ๋‹ˆ๋‹ค")
    }
}

class B: A() {
    override fun shout() {
        println("B๊ฐ€ ์†Œ๋ฆฌ์นฉ๋‹ˆ๋‹ค")
    }
}

class C: A() {
    override fun shout() {
        println("C๊ฐ€ ์†Œ๋ฆฌ์นฉ๋‹ˆ๋‹ค")
    }
}

class UsingGeneric<T: A> (val t: T) {
    fun doShouting() {
        t.shout()
    }
}

ํ•จ์ˆ˜์— ์ ์šฉ

fun main() {
		...
		
    doShouting(B())
}

fun <T: A> doShouting(t: T) {
    t.shout()
}

๋ฆฌ์ŠคํŠธ

์—ฌ๋Ÿฌ ๊ฐœ์˜ ๋ฐ์ดํ„ฐ๋ฅผ ์›ํ•˜๋Š” ์ˆœ์„œ๋กœ ๋„ฃ์–ด ๊ด€๋ฆฌ

๋ฆฌ์ŠคํŠธ์—๋Š” ๋‘ ๊ฐ€์ง€์˜ ์ข…๋ฃŒ๊ฐ€ ์กด์žฌ

List<out T>

  • ์ƒ์„ฑ ์‹œ ๋„ฃ์€ ๊ฐ์ฒด๋ฅผ ๋Œ€์ฒด/์ถ”๊ฐ€/์‚ญ์ œ ๋ถˆ๊ฐ€

  • ์ „์šฉ ํ•จ์ˆ˜: listOf(1, 2, 3)

MutableList<T>

  • ์ƒ์„ฑ ์‹œ ๋„ฃ์€ ๊ฐ์ฒด๋ฅผ ๋Œ€์ฒด/์ถ”๊ฐ€/์‚ญ์ œ ๊ฐ€๋Šฅ

  • ์ „์šฉ ํ•จ์ˆ˜: mutableListOf(1, 2, 3)

  • ์š”์†Œ ์ถ”๊ฐ€(add), ์‚ญ์ œ(remove, removeAt) ๊ธฐ๋Šฅ ์™ธ์—๋„

    • ๋ฌด์ž‘์œ„ ์„ž๊ธฐ(shuffle), ์ •๋ ฌ(sort) ๊ธฐ๋Šฅ๋„ ์ œ๊ณต

fun main() {
  	val a = listOf("์‚ฌ๊ณผ", "๋”ธ๊ธฐ", "๋ฐฐ")
    println(a[1]) // ๋”ธ๊ธฐ
    
    for (fruit in a) {
        print("${fruit}:")
    }
    println() // ์‚ฌ๊ณผ:๋”ธ๊ธฐ:๋ฐฐ:
    
    var b = mutableListOf(6, 3, 1)
    println(b) // [6, 3, 1]
    
    b.add(4)
    println(b) // [6, 3, 1, 4]
    
    b.add(2, 8) // [6, 3, 8, 1, 4]
    println(b)
    
    b.removeAt(1)
    println(b) // [6, 8, 1, 4]
    
    b.shuffle()
    println(b) // [6, 1, 4, 8]
    
    b.sort()
    println(b) // [1, 4, 6, 8]
}

๋ฌธ์ž์—ด ๋‹ค๋ฃจ๊ธฐ

๋ฌธ์ž์—ด ๋ณ€ํ˜•

val test1 = "Test.Kotlin.String"
    
println(test1.length) // 18

println(test1.toLowerCase()) // test.kotlin.string
println(test1.toUpperCase()) // TEST.KOTLIN.STRING

val test2 = test1.split(".")
println(test2) // [Test, Kotlin, String]

println(test2.joinToString()) // Test, Kotlin, String
println(test2.joinToString("-")) // Test-Kotlin-String

println(test1.substring(5..10)) // Kotlin

๋ฌธ์ž์—ด ํ™•์ธ

val nullString: String? = null
val emptyString = ""
val blankString = " "
val normalString = "A"

println(nullString.isNullOrEmpty()) // true
println(emptyString.isNullOrEmpty()) // true
println(blankString.isNullOrEmpty()) // false
println(normalString.isNullOrEmpty()) // false
println()

println(nullString.isNullOrBlank()) // true
println(emptyString.isNullOrBlank()) // true
println(blankString.isNullOrBlank()) // true
println(normalString.isNullOrBlank()) // false

๋ฌธ์ž์—ด ํƒ์ƒ‰

fun main() {
   	val test3 = "kotlin.kt"
    val test4 = "java.java"
    
    println(test3.startsWith("java")) // false
  	println(test4.startsWith("java")) // true
    
    println(test3.endsWith(".kt")) // true
    println(test4.endsWith(".kt")) // false
    
    println(test3.contains("lin")) // true
    println(test4.contains("lin")) // false
}

Null ์ฒ˜๋ฆฌ์™€ ๋™์ผ์„ฑํ™•์ธ

Null ์ฒ˜๋ฆฌ

null์„ ์ฒ˜๋ฆฌํ•˜๋Š” ๋ฐฉ๋ฒ•๋“ค

?. : null safe operator

  • ์ฐธ์กฐ์—ฐ์‚ฐ์ž ์‹คํ–‰ ์ „ ๋จผ์ € ๊ฐ์ฒด๊ฐ€ null์ธ์ง€ ํ™•์ธํ•˜๊ณ , ๊ฐ์ฒด null ์—ฌ๋ถ€์— ๋”ฐ๋ผ ๋’ค์— ์˜ค๋Š” ๊ตฌ๋ฌธ ์‹คํ–‰ ์—ฌ๋ถ€ ํŒ๋‹จ

  • sample?.toUpperCase()

?: : elvis operator

  • ๊ฐ์ฒด๊ฐ€ null์ด ์•„๋‹ˆ๋ผ๋ฉด ๊ทธ๋Œ€๋กœ ์‚ฌ์šฉํ•˜์ง€๋งŒ, null์ด๋ผ๋ฉด ์—ฐ์‚ฐ์ž ์šฐ์ธก ๊ฐ์ฒด๋กœ ๋Œ€์ฒด

  • sample?:โ€defaultโ€

!!. : non-null assertion operator

  • ์ฐธ์กฐ์—ฐ์‚ฐ์ž ์‚ฌ์šฉ ์‹œ null ์—ฌ๋ถ€๋ฅผ ์ปดํŒŒ์ผ ์‹œ ํ™•์ธํ•˜์ง€ ์•Š๋„๋ก ํ•˜์—ฌ ๋Ÿฐํƒ€์ž„ ์‹œ NPE์ด ๋ฐœ์ƒํ•˜๋„๋ก ์˜๋„์ ์œผ๋กœ ๋ฐฉ์น˜

  • sample!!.toUpperCase()

var a: String? = null

println(a?.toUpperCase()) // null

println(a?:"default".toUpperCase()) // DEFAULT

println(a!!.toUpperCase()) // NPE!!!

null safe ์—ฐ์‚ฐ์ž๋Š” ์Šค์ฝ”ํ”„ ํ•จ์ˆ˜์™€ ์‚ฌ์šฉํ•˜๋ฉด ํŽธ๋ฆฌ

  • null ์ฒดํฌ๋ฅผ ์œ„ํ•ด if ๋ณด๋‹ค ํŽธ๋ฆฌํ•œ ๊ธฐ๋Šฅ

fun main() {
	var a: String? = null
    
	a?.run { // a๊ฐ€ null์ด๋ฏ€๋กœ ์Šค์ฝ”ํ”„ ์ „์ฒด๊ฐ€ ๋ฏธ์ˆ˜ํ–‰
        println(toUpperCase())
        println(toLowerCase())
    }
    
    var b: String? = "Kotlin example"
    
	b?.run { // a๊ฐ€ null์ด๋ฏ€๋กœ ์Šค์ฝ”ํ”„ ์ „์ฒด๊ฐ€ ๋ฏธ์ˆ˜ํ–‰
        println(toUpperCase())
        println(toLowerCase())
    }
}

๋™์ผ์„ฑ ํ™•์ธ

๋‚ด์šฉ์˜ ๋™์ผ์„ฑ

  • ์ž๋™์œผ๋กœ ํŒ๋‹จ๋˜๋Š” ๊ฒƒ์ด ์•„๋‹Œ ์ฝ”ํ‹€๋ฆฐ ๋ชจ๋“  ํด๋ž˜์Šค๊ฐ€ ๋‚ด๋ถ€์ ์œผ๋กœ ์ƒ์†๋ฐ›๋Š” Any ์ตœ์ƒ์œ„ ํด๋ž˜์Šค์˜ equals() ํ•จ์ˆ˜๊ฐ€ ๋ฐ˜ํ™˜ํ•˜๋Š” Boolean ๊ฐ’์œผ๋กœ ํŒ๋‹จ

  • a == b

๊ฐ์ฒด์˜ ๋™์ผ์„ฑ

  • a === b

fun main() {
    var a = Product("์ฝœ๋ผ", 1000)
    var b = Product("์ฝœ๋ผ", 1000)
    var c = a
    var d = Product("์‚ฌ์ด๋‹ค", 1000)
    
    println(a == b) // true
    println(a === b) // false

    println(a == c) // true
    println(a === c) // true
    
    println(a == d) // false
    println(a === d) // false
}

class Product(val name: String, val price: Int) {
    override fun equals(other: Any?): Boolean {
        if (other is Product) {
            return other.name == name && other.price == price
        } 
        return false
    }
}

ํ•จ์ˆ˜์˜ argument๋ฅผ ๋‹ค๋ฃจ๋Š” ๋ฐฉ๋ฒ•๊ณผ infix ํ•จ์ˆ˜

โœ… default arguments

ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ๋ฐ›์•„์•ผ ํ•˜๋Š” ํ•จ์ˆ˜์ด์ง€๋งŒ ํŒŒ๋ผ๋ฏธํ„ฐ๊ฐ€ ์—†๋”๋ผ๋„ ๊ธฐ๋ณธ๊ฐ’์œผ๋กœ ๋™์ž‘ํ•ด์•ผ ํ•  ๊ฒฝ์šฐ ์‚ฌ์šฉ

fun main() {
    deliveryItem("์งฌ๋ฝ•") // ์งฌ๋ฝ•, 1๊ฐœ๋ฅผ ์ง‘์— ๋ฐฐ๋‹ฌํ•˜์˜€์Šต๋‹ˆ๋‹ค.
    deliveryItem("์ฑ…", 3) // ์ฑ…, 3๊ฐœ๋ฅผ ์ง‘์— ๋ฐฐ๋‹ฌํ•˜์˜€์Šต๋‹ˆ๋‹ค.
    deliveryItem("๋…ธํŠธ๋ถ", 30, "ํ•™๊ต") // ๋…ธํŠธ๋ถ, 30๊ฐœ๋ฅผ ํ•™๊ต์— ๋ฐฐ๋‹ฌํ•˜์˜€์Šต๋‹ˆ๋‹ค.
}

fun deliveryItem(name: String, count: Int = 1, destination: String = "์ง‘") {
    println("${name}, ${count}๊ฐœ๋ฅผ ${destination}์— ๋ฐฐ๋‹ฌํ•˜์˜€์Šต๋‹ˆ๋‹ค.")
}

๋‹จ, ํŒŒ๋ผ๋ฏธํ„ฐ์˜ ์ค‘๊ฐ„์„ ๋น„์šฐ๋ฉด ๋™์ž‘ํ•˜์ง€ ์•Š๋Š”๋‹ค.

  • ์ด ๊ฒฝ์šฐ์—๋Š” named arguments ์‚ฌ์šฉ

  • ํŒŒ๋ผ๋ฏธํ„ฐ์˜ ์ˆœ์„œ์™€ ๊ด€๊ณ„์—†์ด ํŒŒ๋ผ๋ฏธํ„ฐ ์ด๋ฆ„์„ ์‚ฌ์šฉํ•˜์—ฌ ์ง์ ‘ ํŒŒ๋ผ๋ฏธํ„ฐ ๊ฐ’์„ ํ• ๋‹น

deliveryItem("๋…ธํŠธ๋ถ", destination = "ํ•™๊ต")

โœ… variable number of arguments (vararg)

๊ฐ™์€ ์ž๋ฃŒํ˜•์„ ๊ฐœ์ˆ˜์— ์ƒ๊ด€์—†์ด ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ๋ฐ›๊ณ  ์‹ถ์„ ๊ฒฝ์šฐ ์‚ฌ์šฉ

fun main() {
	sum(1, 2, 3, 4)
}

fun sum(vararg numbers: Int) {
    var sum = 0
    
    for (n in numbers) {
        sum += n
    }
    
    print(sum)
}

๊ฐœ์ˆ˜๊ฐ€ ์ง€์ •๋˜์ง€ ์•Š์€ ํŒŒ๋ผ๋ฏธํ„ฐ๋ผ๋Š” ํŠน์ง•์ด ์žˆ์œผ๋ฏ€๋กœ ๋‹ค๋ฅธ ํŒŒ๋ผ๋ฏธํ„ฐ์™€ ๊ฐ™์ด ์‚ฌ์šฉํ•  ๊ฒฝ์šฐ์—๋Š” ๋งจ ๋งˆ์ง€๋ง‰์— ์„ ์–ธ

  • fun sample(text: String, vararg x: Int)

โœ… infix ํ•จ์ˆ˜

  • ํ•จ์ˆ˜ ์ •์˜ ์‹œ ์•ž์— infix ๋ฅผ ๋ถ™์ธ ํ›„, ํ•จ์ˆ˜ ์ด๋ฆ„์„ infix ํ•จ์ˆ˜๊ฐ€ ์ ์šฉ๋  ์ž๋ฃŒํ˜•.์ด๋ฆ„์œผ๋กœ ์ง€์ •

fun main() {
    /**
     * 6: infix ํ•จ์ˆ˜๊ฐ€ ์ ์šฉ๋˜๋Š” ๊ฐ์ฒด ์ž์‹ (this)
     * 4: ํŒŒ๋ผ๋ฏธํ„ฐ์ธ x
     */
	println(6 multiply 4)
    // ๋™์ผํ•˜๊ฒŒ ๋™์ž‘
    println(6 multiply(4))
}

infix fun Int.multiply(x: Int): Int = this * x

์ฐธ๊ณ ๋กœ, ํด๋ž˜์Šค ์•ˆ์—์„œ infix ํ•จ์ˆ˜ ์„ ์–ธ ์‹œ ์ ์šฉ ํด๋ž˜์Šค๊ฐ€ ์ž๊ธฐ ์ž์‹ ์ด๋ฏ€๋กœ ํด๋ž˜์Šค ์ด๋ฆ„์€ ์ƒ๋žต ๊ฐ€๋Šฅ

  • infix fun multiply(x: Int): Int = this * x


์ค‘์ฒฉ ํด๋ž˜์Šค์™€ ๋‚ด๋ถ€ ํด๋ž˜์Šค

Nested Class(์ค‘์ฒฉ ํด๋ž˜์Šค)

ํ˜•ํƒœ๋งŒ ๋‚ด๋ถ€์— ์กด์žฌํ•  ๋ฟ, ์™ธ๋ถ€ ํด๋ž˜์Šค์˜ ๋‚ด์šฉ์„ ๊ณต์œ ํ•  ์ˆ˜ ์—†๋Š” ๋ณ„๊ฐœ์˜ ํด๋ž˜์Šค

Inner Class (๋‚ด๋ถ€ ํด๋ž˜์Šค)

์™ธ๋ถ€ ํด๋ž˜์Šค ๊ฐ์ฒด ์•ˆ์—์„œ ์‚ฌ์šฉ๋˜๋Š” ํด๋ž˜์Šค๋กœ ์™ธ๋ถ€ ํด๋ž˜์Šค์˜ ์†์„ฑ๊ณผ ํ•จ์ˆ˜ ์‚ฌ์šฉ ๊ฐ€๋Šฅ

  • ํ˜ผ์ž์„œ ๊ฐ์ฒด๋ฅผ ๋งŒ๋“ค ์ˆ˜๋Š” ์—†๊ณ , ์™ธ๋ถ€ ํด๋ž˜์Šค์˜ ๊ฐ์ฒด๊ฐ€ ์žˆ์–ด์•ผ๋งŒ ์ƒ์„ฑ๊ณผ ์‚ฌ์šฉ์ด ๊ฐ€๋Šฅ

fun main() {
  	Outer.Nested().introduce() // Nested Class
    val nested = Outer.Nested() // Nested Class 
    nested.introduce()
    
    val outer = Outer()
    val inner = outer.Inner()
    
    inner.introduceInner() // Inner Class
    inner.introduceOuter() // Outer Class
    
    outer.text = "Changed Outer Class"
    inner.introduceOuter() // Changed Outer Class
}

class Outer {
    var text = "Outer Class"
    
    class Nested {
        fun introduce() {
            println("Nested Class")
        }
    }
    
    inner class Inner {
        var text = "Inner Class"
        
        fun introduceInner() {
            println(text)
        }
        
        fun introduceOuter() {
            println(this@Outer.text)
        }
    }
}

Data Class & Enum Class

โœ… Data Class

๋ฐ์ดํ„ฐ๋ฅผ ๋‹ค๋ฃจ๋Š”๋ฐ ์ตœ์ ํ™”๋œ ํด๋ž˜์Šค

5๊ฐ€์ง€ ๊ธฐ๋Šฅ์„ ๋‚ด๋ถ€์ ์œผ๋กœ ์ž๋™ ์ƒ์„ฑ

  • equals(): ๋‚ด์šฉ์˜ ๋™์ผ์„ฑ ํŒ๋‹จ

  • hashcode(): ๊ฐ์ฒด ๋‚ด์šฉ์—์„œ ๊ณ ์œ ํ•œ ์ฝ”๋“œ๋ฅผ ์ƒ์„ฑ

  • toString(): ํฌํ•จ๋œ ์†์„ฑ์„ ๋ณด๊ธฐ ์‰ฝ๊ฒŒ ํ‘œํ˜„

  • copy()

    // ํŒŒ๋ผ๋ฏธํ„ฐ๊ฐ€ ์—†๋Š” ๊ฒฝ์šฐ ๋˜‘๊ฐ™์€ ๋‚ด์šฉ์œผ๋กœ ์ƒ์„ฑ
    val a = Data("A", 7)
    val b = a.copy()
    
    // ํŒŒ๋ผ๋ฏธํ„ฐ๊ฐ€ ์žˆ์œผ๋ฉด ํ•ด๋‹น ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ๊ต์ฒดํ•˜์—ฌ ์ƒ์„ฑ
    val a = Data("A", 7)
    val b = a.copy("B")

Example

fun main() {
  	val a = General("๋ณด์˜", 212)
    
    println(a == General("๋ณด์˜", 212)) // false
    println(a.hashCode()) // 20132171
    println(a) // General@133314b
    
    val b = Data("๋ฃจ๋‹ค", 306)

    println(b == Data("๋ฃจ๋‹ค", 306)) // true
    println(b.hashCode()) // 46909878
    println(b) // Data(name=๋ฃจ๋‹ค, id=306)
    
    println(b.copy()) // Data(name=๋ฃจ๋‹ค, id=306)
    println(b.copy("์•„๋ฆฐ")) // Data(name=์•„๋ฆฐ, id=306)
    println(b.copy(id = 618)) // Data(name=๋ฃจ๋‹ค, id=618)
}

class General(val name: String, val id: Int)

data class Data(val name: String, val id: Int)

โœ… componentX(): ์†์„ฑ์„ ์ˆœ์„œ๋Œ€๋กœ ๋ฐ˜ํ™˜

Data("A", 7)
component1() -> "A"
component2() -> 7

listOf(Data("A", 7), Data("B", 1))
component1() -> Data("A", 7)
component2() -> Data("B", 1)

Example

fun main() {
	val list = listOf(Data("๋ณด์˜", 212),
                     Data("๋ฃจ๋‹ค", 306),
                     Data("์•„๋ฆฐ", 618))
    
    for ((a, b) in list) {
        // ๋‚ด๋ถ€์ ์œผ๋กœ component1(), component2() ํ•จ์ˆ˜ ์‚ฌ์šฉ
        println("${a}, ${b}")
    }
}

class General(val name: String, val id: Int)

data class Data(val name: String, val id: Int)

โœ… Enum Class

enumerated type (์—ด๊ฑฐํ˜•)

enum ํด๋ž˜์Šค ์•ˆ์˜ ๊ฐ์ฒด๋“ค์€ ๊ด€ํ–‰์ ์œผ๋กœ ์ƒ์ˆ˜๋ฅผ ๋‚˜ํƒ€๋‚ผ ๋•Œ ์‚ฌ์šฉํ•˜๋Š” ๋Œ€๋ฌธ์ž๋กœ ๊ธฐ์ˆ 

  • enum์˜ ๊ฐ์ฒด๋“ค์€ ๊ณ ์œ ํ•œ ์†์„ฑ์„ ๊ฐ€์งˆ ์ˆ˜ ์žˆ์Œ

fun main() {
	var state = State.SING
    println(state) // SING (toString์„ ํ†ตํ•ด ์ƒํƒœ ๊ฐ์ฒด์˜ ์ด๋ฆ„์ด ์ถœ๋ ฅ)
    
    state = State.SLEEP
    println(state.isSleeping()) // true
    
    state = State.EAT
    println(state.message) // ๋ฐฅ์„ ๋จน์Šต๋‹ˆ๋‹ค
}

enum class State(val message: String) {
    SING("๋…ธ๋ž˜๋ฅผ ๋ถ€๋ฆ…๋‹ˆ๋‹ค"),
    EAT("๋ฐฅ์„ ๋จน์Šต๋‹ˆ๋‹ค"),
    SLEEP("์ž ์„ ์žก๋‹ˆ๋‹ค");
    
   fun isSleeping() = this == State.SLEEP
}

Set & Map

โœ… Set

์ˆœ์„œ๊ฐ€ ์ •๋ ฌ๋˜์ง€ ์•Š์œผ๋ฉฐ, ์ค‘๋ณต์ด ํ—ˆ์šฉ๋˜์ง€ ์•Š๋Š” ์ปฌ๋ ‰์…˜

fun main() {
	  val a = mutableSetOf("๊ทค", "๋ฐ”๋‚˜๋‚˜", "ํ‚ค์œ„")
    
    for (item in a) {
        println("${item}") // ๊ทค ๋ฐ”๋‚˜๋‚˜ ํ‚ค์œ„
    }
    
    a.add("์ž๋ชฝ")
    println(a) // [๊ทค, ๋ฐ”๋‚˜๋‚˜, ํ‚ค์œ„, ์ž๋ชฝ]
    
    a.remove("๋ฐ”๋‚˜๋‚˜")
    println(a) // [๊ทค, ํ‚ค์œ„, ์ž๋ชฝ]
    
    println(a.contains("๊ทค")) // true
}

โœ… Map

๊ฐ์ฒด๋ฅผ ๋„ฃ์„ ๋•Œ ๊ทธ ๊ฐ์ฒด๋ฅผ ์ฐพ์•„๋‚ผ ์ˆ˜ ์žˆ๋Š” Key๋ฅผ ์Œ์œผ๋กœ ๋„ฃ์–ด์ฃผ๋Š” ์ปฌ๋ ‰์…˜

fun main() {
	val a = mutableMapOf("๋ ˆ๋“œ๋ฒจ๋ฒณ" to "์ŒํŒŒ์ŒํŒŒ",
                         "ํŠธ์™€์ด์Šค" to "FANCY",
                         "ITZY" to "ICY")
    
    for (entry in a) {
        println("${entry.key} : ${entry.value}") // ๋ ˆ๋“œ๋ฒจ๋ฒณ : ์ŒํŒŒ์ŒํŒŒ, ํŠธ์™€์ด์Šค : FANCY, ITZY : ICY
    }
    
    a.put("์˜ค๋งˆ์ด๊ฑธ", "๋ฒˆ์ง€")
    println(a) // {๋ ˆ๋“œ๋ฒจ๋ฒณ=์ŒํŒŒ์ŒํŒŒ, ํŠธ์™€์ด์Šค=FANCY, ITZY=ICY, ์˜ค๋งˆ์ด๊ฑธ=๋ฒˆ์ง€}
    
    a.remove("ITZY")
    println(a) // {๋ ˆ๋“œ๋ฒจ๋ฒณ=์ŒํŒŒ์ŒํŒŒ, ํŠธ์™€์ด์Šค=FANCY, ์˜ค๋งˆ์ด๊ฑธ=๋ฒˆ์ง€}
    
    println(a["๋ ˆ๋“œ๋ฒจ๋ฒณ"]) // ์ŒํŒŒ์ŒํŒŒ
}

์ปฌ๋ ‰์…˜ ํ•จ์ˆ˜

forEach

  • ์ปฌ๋ ‰์…˜ ์•ˆ์—์„œ ๋ชจ๋“  ์›์†Œ๋ฅผ it ์„ ํ†ตํ•ด ์ฐธ์กฐ

  • collection.forEach { println(it) }

filter

  • ์ปฌ๋ ‰์…˜ ์•ˆ์—์„œ ์กฐ๊ฑด์— ๋งž๋Š” ์›์†Œ๋ฅผ ๋ชจ์•„์„œ ๋‹ค์‹œ ์ปฌ๋ ‰์…˜์œผ๋กœ ๋ฐ˜ํ™˜

  • collection.filter { it < 4 }

map

  • ์ˆ˜์‹์„ ํ†ตํ•ด ์—ฐ์‚ฐ๋œ ๊ฒฐ๊ณผ๋ฅผ ์ปฌ๋ ‰์…˜์œผ๋กœ ๋ฐ˜ํ™˜

  • collection.map { it * 2 }

any

  • ํ•˜๋‚˜๋ผ๋„ ์กฐ๊ฑด์— ๋งž์œผ๋ฉด true

  • collection.any { it == 0 }

all

  • ๋ชจ๋‘ ์กฐ๊ฑด์— ๋งž์œผ๋ฉด true

  • collection.all { it == 0 }

none

  • ํ•˜๋‚˜๋ผ๋„ ์กฐ๊ฑด์— ๋งž์ง€ ์•Š์œผ๋ฉด true

  • collection.none { it == 0 }

first

  • collection.first(): ์ปฌ๋ ‰์…˜์˜ ์ฒซ ๋ฒˆ์งธ ์•„์ดํ…œ ๋ฐ˜ํ™˜

  • collection.first{ it > 3 } : ์กฐ๊ฑด์— ๋งž๋Š” ์ฒซ๋ฒˆ์งธ ์•„์ดํ…œ ๋ฐ˜ํ™˜

  • find ํ•จ์ˆ˜๋กœ ๋Œ€์ฒด ๊ฐ€๋Šฅ

last

  • collection.last{ it > 3 } : ์กฐ๊ฑด์— ๋งž๋Š” ๋งˆ์ง€๋ง‰ ์•„์ดํ…œ ๋ฐ˜ํ™˜

  • findLast ํ•จ์ˆ˜๋กœ ๋Œ€์ฒด ๊ฐ€๋Šฅ

โš ๏ธ first, last ํ•จ์ˆ˜๋Š” ์กฐ๊ฑด์— ๋งž๋Š” ๊ฐ์ฒด๊ฐ€ ์—†๋Š” ๊ฒฝ์šฐ NoSuchElementException ๋ฐœ์ƒ

  • ์ด ๊ฒฝ์šฐ firstOrNull, lastOrNull ํ™œ์šฉ

count

  • collection.count() : ์ปฌ๋ ‰์…˜์˜ ๋ชจ๋“  ์•„์ดํ…œ ๊ฐœ์ˆ˜ ๋ฐ˜ํ™˜

  • collection.count { it > 7 } : ์กฐ๊ฑด์— ๋งž๋Š” ์•„์ดํ…œ ๊ฐœ์ˆ˜ ๋ฐ˜ํ™˜

fun main() {
	val nameList = listOf("๋ฐ•์ˆ˜์˜", "๊น€์ง€์ˆ˜", "๊น€๋‹คํ˜„", "์‹ ์œ ๋‚˜", "๊น€์ง€์šฐ")
    
    nameList.forEach { print(it + " ") } // ๋ฐ•์ˆ˜์˜ ๊น€์ง€์ˆ˜ ๊น€๋‹คํ˜„ ์‹ ์œ ๋‚˜ ๊น€์ง€์šฐ 
    println()
    
    println(nameList.filter { it.startsWith("๊น€") }) // [๊น€์ง€์ˆ˜, ๊น€๋‹คํ˜„, ๊น€์ง€์šฐ]
    println(nameList.map { "์ด๋ฆ„ : " + it }) // [์ด๋ฆ„ : ๋ฐ•์ˆ˜์˜, ์ด๋ฆ„ : ๊น€์ง€์ˆ˜, ์ด๋ฆ„ : ๊น€๋‹คํ˜„, ์ด๋ฆ„ : ์‹ ์œ ๋‚˜, ์ด๋ฆ„ : ๊น€์ง€์šฐ]

    println(nameList.any { it == "๊น€์ง€์—ฐ" } ) // false
    println(nameList.all { it.length == 3 } ) // true
    println(nameList.none { it.startsWith("์ด") }) // true
    
    println(nameList.first { it.startsWith("๊น€") }) // ๊น€์ง€์ˆ˜
    println(nameList.last { it.startsWith("๊น€") }) // ๊น€์ง€์šฐ
    println(nameList.count { it.contains("์ง€") }) // 2
}

associateBy

  • list์˜ ์•„์ดํ…œ์—์„œ key๋ฅผ ์ถ”์ถœํ•˜์—ฌ map ์œผ๋กœ ๋ณ€ํ™˜ํ•˜๋Š” ํ•จ์ˆ˜

  • collection.associateBy { it.name }

groupBy

  • key๊ฐ€ ๊ฐ™์€ ์•„์ดํ…œ๋ผ๋ฆฌ ๋ฐฐ์—ด๋กœ ๋ฌถ์–ด map์œผ๋กœ ๋งŒ๋“œ๋Š” ํ•จ์ˆ˜

  • collection.groupBy { it.birthYear }

partition

  • ์•„์ดํ…œ์— ์กฐ๊ฑด์„ ๊ฑธ์–ด ๋‘ ๊ฐœ์˜ ์ปฌ๋ ‰์…˜์œผ๋กœ ๋‚˜๋ˆ„๋Š” ํ•จ์ˆ˜

  • collection.partition { it.birthYear > 2002 }

  • val (over2002, under2002) = collection.partition { it.birthYear > 2002 }

fun main() {
  	data class Person(val name: String, val birthYear: Int)
    
    val personList = listOf(Person("์œ ๋‚˜", 1992),
                            Person("์กฐ์ด", 1996),
                            Person("์ธ„", 1999),
                            Person("์œ ๋‚˜", 2003))
    
    // {1992=Person(name=์œ ๋‚˜, birthYear=1992), 1996=Person(name=์กฐ์ด, birthYear=1996), 1999=Person(name=์ธ„, birthYear=1999), 2003=Person(name=์œ ๋‚˜, birthYear=2003)}
    println(personList.associateBy{ it.birthYear })
    // {์œ ๋‚˜=[Person(name=์œ ๋‚˜, birthYear=1992), Person(name=์œ ๋‚˜, birthYear=2003)], ์กฐ์ด=[Person(name=์กฐ์ด, birthYear=1996)], ์ธ„=[Person(name=์ธ„, birthYear=1999)]}
    println(personList.groupBy{ it.name })
    
    val (over98, under98) = personList.partition { it.birthYear > 1998 }
    println(over98) // [Person(name=์ธ„, birthYear=1999), Person(name=์œ ๋‚˜, birthYear=2003)]
    println(under98) // [Person(name=์œ ๋‚˜, birthYear=1992), Person(name=์กฐ์ด, birthYear=1996)]
}

flatMap

  • ์•„์ดํ…œ๋งˆ๋‹ค ๋งŒ๋“ค์–ด์ง„ ์ปฌ๋ ‰์…˜์„ ํ•ฉ์ณ์„œ ๋ฐ˜ํ™˜ํ•˜๋Š”

  • collection.flatMap { listOf(it * 3, it * 3 }

getOrElse

  • ์ธ๋ฑ์Šค ์œ„์น˜์— ์•„์ดํ…œ์ด ์žˆ์œผ๋ฉด ์•„์ดํ…œ์„ ๋ฐ˜ํ™˜ํ•˜๊ณ , ์•„๋‹Œ ๊ฒฝ์šฐ ์ง€์ •ํ•œ ๊ธฐ๋ณธ๊ฐ’์„ ๋ฐ˜ํ™˜

  • collection.getOrElse(1) { 50 }

zip

  • ์ปฌ๋ ‰์…˜ ๋‘ ๊ฐœ์˜ ์•„์ดํ…œ์„ 1:1๋กœ ๋งค์นญํ•˜์—ฌ ์ƒˆ ์ปฌ๋ ‰์…˜์œผ๋กœ ์ƒ์„ฑ

  • ๊ฒฐ๊ณผ ๋ฆฌ์ŠคํŠธ์˜ ์•„์ดํ…œ ๊ฐœ์ˆ˜๋Š” ๋” ์ž‘์€ ์ปฌ๋ ‰์…˜์„ ๋”ฐ๋ผ๊ฐ

fun main() {
	val numbers = listOf(-3, 7, 2, -10, 1)
    
    println(numbers.flatMap { listOf(it * 10, it + 10) }) // [-30, 7, 70, 17, 20, 12, -100, 0, 10, 11]
    
    println(numbers.getOrElse(1) { 50 }) // 7
    println(numbers.getOrElse(10) { 50 }) // 50
    
    val names = listOf("A", "B", "C", "D")
    println(names zip numbers) // [(A, -3), (B, 7), (C, 2), (D, -10)]
}

๋ณ€์ˆ˜์˜ ๊ณ ๊ธ‰ ๊ธฐ์ˆ 

โœ… ์ƒ์ˆ˜

์ปดํŒŒ์ผ ์‹œ์ ์— ๊ฒฐ์ •๋˜์–ด ๋ฐ”๊ฟ€ ์ˆ˜ ์—†๋Š” ๊ฐ’

const val CONST_A = 1234

์ƒ์ˆ˜๋กœ ์„ ์–ธ๋  ์ˆ˜ ์žˆ๋Š” ๊ฐ’์€ ๊ธฐ๋ณธ ์ž๋ฃŒํ˜•๋งŒ ๊ฐ€๋Šฅ

  • ๋Ÿฐํƒ€์ž„์— ์ƒ์„ฑ๋  ์ˆ˜ ์žˆ๋Š” ์ผ๋ฐ˜์ ์ธ ๋‹ค๋ฅธ ํด๋ž˜์Šค์˜ ๊ฐ์ฒด๋“ค์€ ๋‹ด์„ ์ˆ˜ ์—†๋‹ค.

  • ํด๋ž˜์Šค์˜ ์†์„ฑ์ด๋‚˜ ์ง€์—ญ๋ณ€์ˆ˜๋กœ๋Š” ์‚ฌ์šฉ ๋ถˆ๊ฐ€

๋ฐ˜๋“œ์‹œ companion object ์•ˆ์— ์„ ์–ธํ•˜์—ฌ ๊ฐ์ฒด์˜ ์ƒ์„ฑ๊ณผ ๊ด€๊ณ„์—†์ด ํด๋ž˜์Šค์™€ ๊ด€๊ณ„๋œ ๊ณ ์ •์ ์ธ ๊ฐ’์œผ๋กœ๋งŒ ์‚ฌ์šฉ ๊ฐ€๋Šฅ

  • ๋ณ€์ˆ˜์˜ ๊ฒฝ์šฐ ๋Ÿฐํƒ€์ž„ ์‹œ ๊ฐ์ฒด ์ƒ์„ฑ์— ์‹œ๊ฐ„์ด ๋” ์†Œ์š”๋˜์–ด ์„ฑ๋Šฅ ํ•˜๋ฝ์ด ์žˆ์–ด ์ด๋ฅผ ๋ง‰๊ณ ์ž ์ƒ์ˆ˜๋ฅผ ์‚ฌ์šฉ

Example

fun main() {
	val foodCourt = FoodCourt()
    
    foodCourt.searchPrice(FoodCourt.FOOD_CREAM_PASTA) // ํฌ๋ฆผํŒŒ์Šคํƒ€์˜ ๊ฐ€๊ฒฉ์€ 13000์› ์ž…๋‹ˆ๋‹ค.
    foodCourt.searchPrice(FoodCourt.FOOD_STEAK) // ์Šคํ…Œ์ดํฌ์˜ ๊ฐ€๊ฒฉ์€ 25000์› ์ž…๋‹ˆ๋‹ค.
    foodCourt.searchPrice(FoodCourt.FOOD_PIZZA) // ํ”ผ์ž์˜ ๊ฐ€๊ฒฉ์€ 15000์› ์ž…๋‹ˆ๋‹ค.
}

class FoodCourt {
    fun searchPrice(foodName: String) {
        val price = when(foodName) {
            FOOD_CREAM_PASTA -> 13000
            FOOD_STEAK -> 25000
            FOOD_PIZZA -> 15000
            else -> 0
        }
        
        println("${foodName}์˜ ๊ฐ€๊ฒฉ์€ ${price}์› ์ž…๋‹ˆ๋‹ค.")
    }
    
    companion object {
        const val FOOD_CREAM_PASTA = "ํฌ๋ฆผํŒŒ์Šคํƒ€"
        const val FOOD_STEAK = "์Šคํ…Œ์ดํฌ"
        const val FOOD_PIZZA = "ํ”ผ์ž"
    }
}

โœ… lateinit

์ผ๋ฐ˜ ๋ณ€์ˆ˜๋งŒ ์„ ์–ธํ•˜๊ณ  ์ดˆ๊ธฐ๊ฐ’์˜ ํ• ๋‹น์€ ๋‚˜์ค‘์— ํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•˜๋Š” ํ‚ค์›Œ๋“œ

  • ์ดˆ๊ธฐ๊ฐ’ ํ• ๋‹น ์ „๊นŒ์ง€ ๋ณ€์ˆ˜๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†์Œ(์—๋Ÿฌ ๋ฐœ์ƒ)

  • ๊ธฐ๋ณธ ์ž๋ฃŒํ˜•์—๋Š” ์‚ฌ์šฉ ๋ถˆ๊ฐ€

lateinit var a: Int

lateinit ๋ณ€์ˆ˜์˜ ์ดˆ๊ธฐํ™” ์—ฌ๋ถ€ ํ™•์ธ

::a.isInitialized

Example

fun main() {
	val a = LateInitSample()
    
    println(a.getLateInitText()) // ๊ธฐ๋ณธ๊ฐ’
    a.text = "์ƒˆ๋กœ ํ• ๋‹นํ•œ ๊ฐ’"
    println(a.getLateInitText()) // ์ƒˆ๋กœ ํ• ๋‹นํ•œ ๊ฐ’
}

class LateInitSample {
    lateinit var text: String
    
    fun getLateInitText(): String {
        if (::text.isInitialized) {
            return text
        }
        return "๊ธฐ๋ณธ๊ฐ’"
    }
}

โœ… lazy delegate properties

๋ณ€์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์‹œ์ ๊นŒ์ง€ ์ดˆ๊ธฐํ™”๋ฅผ ์ž๋™์œผ๋กœ ๋Šฆ์ถฐ์ฃผ๋Š” ์ง€์—ฐ ๋Œ€๋ฆฌ์ž ์†์„ฑ

  • ์ฝ”๋“œ์ƒ์œผ๋กœ๋Š” ์ฆ‰์‹œ ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑ ๋ฐ ํ• ๋‹นํ•˜์—ฌ ๋ณ€์ˆ˜๋ฅผ ์ดˆ๊ธฐํ™”ํ•˜๋Š” ํ˜•ํƒœ๋ฅผ ๊ฐ–์ง€๋งŒ

  • ์‹ค์ œ ์‹คํ–‰์‹œ์—๋Š” val ๋ณ€์ˆ˜ ์‚ฌ์šฉ ์‹œ์ ์— ์ดˆ๊ธฐํ™”

val a: Int by lazy { 7 }
...
println(a) // ์ด ์‹œ์ ์— 7๋กœ ์ดˆ๊ธฐํ™”

Example

fun main() {
	val number: Int by lazy {
        println("์ดˆ๊ธฐํ™” ์ง„ํ–‰")
        7
    }	
    
    println("์ฝ”๋“œ ์‹œ์ž‘")
    println(number)
    println(number)
    /**
     * ์ฝ”๋“œ ์‹œ์ž‘
     * ์ดˆ๊ธฐํ™” ์ง„ํ–‰
     * 7
     * 7
     */
}

์ฝ”๋ฃจํ‹ด์„ ํ†ตํ•œ ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ

โœ… ์ฝ”๋ฃจํ‹ด์˜ Scope

์ฝ”๋ฃจํ‹ด์€ ์ œ์–ด๋ฒ”์œ„ ๋ฐ ์‹คํ–‰๋ฒ”์œ„ ์ง€์ • ๊ฐ€๋Šฅ

  • GlobalScope

    • ํ”„๋กœ๊ทธ๋žจ ์–ด๋””์„œ๋‚˜ ์ œ์–ด, ๋™์ž‘์ด ๊ฐ€๋Šฅํ•œ ๊ธฐ๋ณธ ๋ฒ”์œ„

  • CoroutineScope

    • ํŠน์ •ํ•œ ๋ชฉ์ ์˜ Dispatcher๋ฅผ ์ง€์ •ํ•˜์—ฌ ์ œ์–ด ๋ฐ ๋™์ž‘์ด ๊ฐ€๋Šฅํ•œ ๋ฒ”์œ„

โœ… CoroutineScope Dispatcher

  • Dispatchers.Default: ๊ธฐ๋ณธ์ ์ธ ๋ฐฑ๊ทธ๋ผ์šด๋“œ ๋™์ž‘

  • Dispatchers.IO: I/O์— ์ตœ์ ํ™” ๋œ ๋™์ž‘

  • Dispatchers.Main : ๋ฉ”์ธ(UI) ์Šค๋ ˆ๋“œ์—์„œ ๋™์ž‘

๋ชจ๋“  ํ”Œ๋žซํผ์—์„œ ์ง€์›๋˜์ง€๋Š” ์•Š์œผ๋‹ˆ ์ง€์›๋˜๋Š” ํ”Œ๋žซํผ์— ๋”ฐ๋ผ ์‚ฌ์šฉ

์ฝ”๋ฃจํ‹ด์€ ์ด๋Ÿฌํ•œ Scope์—์„œ ์ œ์–ด๋˜๋„๋ก ์ƒ์„ฑ๋  ์ˆ˜ ์žˆ์Œ

// ์ƒ์„ฑ๋œ ์Šค์ฝ”ํ”„์—์„œ
val scope = CoroutineScope(Dispatcher.Defaunt)
// ์ƒˆ๋กœ์šด ์ฝ”๋ฃจํ‹ด ์ƒ์„ฑ
val coroutineA = scope.launch {}
val coroutineB = scope.async {}
  • launch vs. async : ๋ฐ˜ํ™˜๊ฐ’์ด ์žˆ๋Š”์ง€์˜ ์—ฌ๋ถ€

    • launch: ๋ฐ˜ํ™˜๊ฐ’์ด ์—†๋Š” Job ๊ฐ์ฒด

      import kotlinx.coroutines.*
      
      fun main() {
      	
          val scope = GlobalScope
      
          runBlocking { // ์ฝ”๋ฃจํ‹ด์ด ์ข…๋ฃŒ๋  ๋•Œ๊นŒ์ง€ ๋ฉ”์ธ ๋ฃจํ‹ด์„ ์ž ์‹œ ๋Œ€๊ธฐ
              // Job ๊ฐ์ฒด์˜ ์ฝ”๋ฃจํ‹ด ์ƒ์„ฑ
              scope.launch {
                  for (i in 1..5) {
                      println(i)
                  }
              }
              
              // launch๋ฅผ ์ง์ ‘ ์ƒ์„ฑ
              launch {
                  for (i in 6..10) {
                      println(i)
                  }
              }
      	}
      }
    • async: ๋ฐ˜ํ™˜๊ฐ’์ด ์žˆ๋Š” Deffered ๊ฐ์ฒด

      async {
          var sum = 0
          for (i in 1..10) {
              sum++
          }
          sum // ์ด ๊ฐ’์ด ๋ฐ˜ํ™˜
      }

โœ… ๋ฃจํ‹ด์˜ ๋Œ€๊ธฐ๋ฅผ ์œ„ํ•œ ์ถ”๊ฐ€์ ์ธ ํ•จ์ˆ˜๋“ค

์ฝ”๋ฃจํ‹ด ๋‚ด๋ถ€ ๋˜๋Š” runBlocking ๊ฐ™์€ ๋ฃจํ‹ด์˜ ๋Œ€๊ธฐ๊ฐ€ ๊ฐ„์œผํ•œ ๊ตฌ๋ฌธ ์•ˆ์—์„œ๋งŒ ๋™์ž‘ ๊ฐ€๋Šฅ

  • delay(milisecond: Long): ms ๋‹จ์œ„๋กœ ๋ฃจํ‹ด์„ ์ž ์‹œ ๋Œ€๊ธฐ์‹œํ‚ค๋Š” ํ•จ์ˆ˜

  • Job.join(): Job์˜ ์‹คํ–‰์ด ๋๋‚ ๋•Œ๊นŒ์ง€ ๋Œ€๊ธฐํ•˜๋Š” ํ•จ์ˆ˜

  • Deferred.await(): Deferred์˜ ์‹คํ–‰์ด ๋๋‚ ๋•Œ๊นŒ์ง€ ๋Œ€๊ธฐํ•˜๋Š” ํ•จ์ˆ˜

    • Deferred ๊ฒฐ๊ณผ๋„ ๋ฐ˜ํ™˜

import kotlinx.coroutines.*

fun main() {
    
    runBlocking {
        val a = launch {
            for (i in 1..5) {
                println(i)
                delay(10)
            }
        }
        
        val b = async {
            "async ์ข…๋ฃŒ"
        }
        
        println("async ๋Œ€๊ธฐ")
        println(b.await()) // Deferred์˜ ์‹คํ–‰์ด ๋๋‚ ๋•Œ๊นŒ์ง€ ๋Œ€๊ธฐ
        
				println("launch ๋Œ€๊ธฐ")
        a.join() // Job์˜ ์‹คํ–‰์ด ๋๋‚ ๋•Œ๊นŒ์ง€ ๋Œ€๊ธฐ
        println("launch ์ข…๋ฃŒ")
	}
    /**
     * async ๋Œ€๊ธฐ
     * 1
     * async ์ข…๋ฃŒ
     * launch ๋Œ€๊ธฐ
     * 2
     * 3
     * 4
     * 5
     * launch ์ข…๋ฃŒ
     */
}

โœ… ์ฝ”๋ฃจํ‹ด ์‹คํ–‰ ๋„์ค‘ ์ค‘๋‹จํ•˜๊ธฐ

์ฝ”๋ฃจํ‹ด์— cancel()์„ ๊ฑธ์–ด์ฃผ๋ฉด ๋‹ค์Œ ๋‘ ๊ฐ€์ง€ ์กฐ๊ฑด์ด ๋ฐœ์ƒํ•˜๋ฉฐ ์ฝ”๋ฃจํ‹ด์„ ์ค‘๋‹จ ๊ฐ€๋Šฅ

  • ์ฝ”๋ฃจํ‹ด ๋‚ด๋ถ€์˜ delay() ํ•จ์ˆ˜ ๋˜๋Š” yield() ํ•จ์ˆ˜๊ฐ€ ์‚ฌ์šฉ๋œ ์œ„์น˜๊นŒ์ง€ ์ˆ˜ํ–‰๋œ ๋’ค ์ข…๋ฃŒ

  • cancel()๋กœ ์ธํ•ด ์†์„ฑ์ธ isActive๊ฐ€ false ๋˜๋ฏ€๋กœ ์ด๋ฅผ ํ™•์ธํ•˜์—ฌ ์ˆ˜๋™์œผ๋กœ ์ข…๋ฃŒ

import kotlinx.coroutines.*

fun main() {
    
    runBlocking {
        val a = launch {
            for (i in 1..5) {
                println(i)
                delay(10)
            }
        }
        
        val b = async {
            "async ์ข…๋ฃŒ"
        }
        
        println("async ๋Œ€๊ธฐ")
        println(b.await())
        
		println("launch ์ทจ์†Œ")
        a.cancel()
        println("launch ์ข…๋ฃŒ")
	}
    /**
     * async ๋Œ€๊ธฐ
     * 1
     * async ์ข…๋ฃŒ
     * launch ์ทจ์†Œ
     * launch ์ข…๋ฃŒ
     */
}

โœ… ์ œํ•œ์‹œ๊ฐ„ ๋‚ด์— ์ˆ˜ํ–‰๋˜๋ฉด ๊ฒฐ๊ณผ๊ฐ’์„ ์•„๋‹Œ ๊ฒฝ์šฐ null ๋ฐ˜ํ™˜ํ•˜๋Š” withTimeoutOrNull()

import kotlinx.coroutines.*

fun main() {
    
    runBlocking {
        var result = withTimeoutOrNull(50) {
            for (i in 1..10) {
                println(i)
                delay(10)
            }
            "Finish"
        }
        println(result)
        /*
         * 1
         * 2
         * 3
         * null
         */
	}
}

Kotlin Steps

JPA Entity ์ฝ”ํ‹€๋ฆฐ์Šค๋Ÿฝ๊ฒŒ ์‚ฌ์šฉํ•˜๊ธฐ

Kotlin์—์„œ JPA Entity ์„ค๊ณ„ํ•˜๊ธฐ

๐Ÿคท๐Ÿปโ€โ™‚๏ธ Data Calss ํ™œ์šฉ?

Entity์˜ ๋™๋“ฑ์„ฑ ์ฒดํฌ๋Š” ๋ชจ๋“  ํ”„๋กœํผํ‹ฐ๋ฅผ ๋น„๊ตํ•˜๋Š”๊ฒŒ ์•„๋‹ˆ๋ผ ์‹๋ณ„์ž๋ฅผ ํ†ตํ•ด์„œ๋งŒ ๋น„๊ต

  • equals์™€ hashCode๋ฅผ ๋”ฐ๋กœ ์žฌ์ •์˜๋ฅผ ํ•˜์ง€ ์•Š์œผ๋ฉด ์ฐธ์กฐ ๋น„๊ต๋ฅผ ํ†ตํ•ด ๋™์ผ์„ฑ ํ™•์ธ์„ ํ•˜๋ฏ€๋กœ

    • ์‹๋ณ„์ž๋ฅผ ํ†ตํ•œ ๋™๋“ฑ์„ฑ ํŒ๋‹จ์„ ์ œ๊ณตํ•˜๋ ค๋ฉด equals์™€ hashCode ์žฌ์ •์˜ ํ•„์š”

๐Ÿคท๐Ÿปโ€โ™‚๏ธ lateinit ์„ ์‚ฌ์šฉํ•œ ์ดˆ๊ธฐํ™” ๋ฏธ๋ฃจ๊ธฐ?

์ดˆ๊ธฐํ™”๋ฅผ ์ตœ๋Œ€ํ•œ ๋’ค๋กœ ๋Šฆ์ถฐ์„œ ์„ฑ๋Šฅ ๋ฐ ํšจ์œจ์„ฑ์„ ๋†’ํžˆ๋ ค๋Š” ์šฉ๋„๋กœ ์‚ฌ์šฉ

  • ์ผ๋ฐ˜์ ์œผ๋กœ ์—ฐ๊ด€๊ด€๊ณ„ ์—†์ด Column๋งŒ ์กด์žฌํ•˜๋Š” ๊ฒฝ์šฐ lateinit๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š์Œ

  • ํ•˜์ง€๋งŒ, ์—ฐ๊ด€๊ด€๊ณ„๋ฅผ ์ •์˜ํ•˜๋Š” ๊ฒฝ์šฐ lateinit ์ •์˜๊ฐ€ ํ•„์š”ํ•œ๋ฐ

    • ์˜์†ํ™”๋œ ์—”ํ‹ฐํ‹ฐ๋ฅผ ์กฐํšŒํ•  ๋•Œ๋Š” JPA๊ฐ€ lateinit ํ•„๋“œ๋ฅผ ์ดˆ๊ธฐํ™”ํ•ด ์ฃผ์ง€๋งŒ

    • ์ด์ œ ๋ง‰ ์ƒ์„ฑํ•œ Entity๋Š” JPA๊ฐ€ lateinit ํ•„๋“œ๋ฅผ ์ดˆ๊ธฐํ™” ํ•ด์ฃผ์ง€ ์•Š์•˜์œผ๋ฏ€๋กœ ์—”ํ‹ฐํ‹ฐ ๊ทธ๋ž˜ํ”„ ํƒ์ƒ‰ ์‹œ์ ์— ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒ

๐Ÿ™†๐Ÿปโ€โ™‚๏ธ lateinit์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ณ  Java์ฒ˜๋Ÿผ ์—ฐ๊ด€๊ด€๊ณ„๋ฅผ ์ •์˜ํ•˜๋ ค๋ฉด?

์—”ํ‹ฐํ‹ฐ ์ž์ฒด๋ฅผ ๋„ฃ์–ด์ฃผ๊ธฐ

@Entity
class Board(
  title: String,
  writer: User, // ์ƒ์„ฑ ์‹œ์ ์— ์ƒ์„ฑ์ž ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ์ž‘์„ฑ์ž๋ฅผ ๋ฐ›์•„์„œ ์ดˆ๊ธฐํ™”
) {
  @Id
  var id: UUID = UUID.randomUUID()

  @Column
  var title: String = title

  @ManyToOne(fetch = FetchType.LAZY, optional = false)
  var writer: User = writer
}

Example

Entity

PrimaryKeyEntity.kt

@MappedSuperclass
abstract class PrimaryKeyEntity : Persistable<UUID> {
    @Id
    @Column(columnDefinition = "uuid")
    private val id: UUID = UlidCreator.getMonotonicUlid().toUuid()

    @Transient
    private var _isNew = true

    override fun getId(): UUID = id
        
        /**
            * Persistable ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๊ตฌํ˜„ํ•œ Entity๋ฅผ ์˜์†ํ™” ํ•˜๋ คํ•˜๋ฉด, 
            * JpaPersistableEntityInformation.isNew ํ•จ์ˆ˜๊ฐ€ ํ˜ธ์ถœ๋˜๋ฉฐ 
            * ๋‚ด๋ถ€์ ์œผ๋กœ Persistable.isNew ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœ
            */
    override fun isNew(): Boolean = _isNew
        
        // PrimaryKeyEntity ์ƒ์†๋ฐ›์€ ์—”ํ‹ฐํ‹ฐ๋“ค์ด ๊ณตํ†ต์œผ๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” 
        // equals, hashCode ์žฌ์ •์˜
    override fun equals(other: Any?): Boolean {
        if (other == null) {
            return false
        }
                
                // ์ง€์—ฐ ์กฐํšŒ๋กœ ์ธํ•ด Entity๊ฐ€ ์‹คํ–‰๋˜๊ธฐ ์ „๊นŒ์ง€๋Š” Proxy ๊ฐ์ฒด๋ฅผ ๋ฏธ๋ฆฌ ์ƒ์„ฑํ•˜๋ฏ€๋กœ 
                // ํ”„๋ก์‹œ ํƒ€์ž…๊นŒ์ง€ ๊ฐ™์ด ๊ณ ๋ ค
        if (other !is HibernateProxy && 
                        this::class != other::class) {
            return false
        }

        return id == getIdentifier(other)
    } 

    private fun getIdentifier(obj: Any): Serializable {
        return if (obj is HibernateProxy) {
            // ํ”„๋ก์‹œ ๊ฐ์ฒด์ผ ๊ฒฝ์šฐ ์‹๋ณ„์ž ์ •๋ณด๊ฐ€ ์กด์žฌํ•˜๋Š” ๊ณณ์—์„œ ์‹๋ณ„์ž๋ฅผ ๊ฐ€์ ธ์˜ด
            obj.hibernateLazyInitializer.identifier
        } else {
            (obj as PrimaryKeyEntity).id
        }
    }

    override fun hashCode() = Objects.hashCode(id)

    /**
        * JPA delete ๋ฉ”์„œ๋“œ์—์„œ๋Š” ์ƒˆ๋กœ์šด ์—”ํ‹ฐํ‹ฐ๋ผ๋ฉด return ์ฒ˜๋ฆฌํ•˜๋‹ค ๋ณด๋‹ˆ
        * ์—”ํ‹ฐํ‹ฐ๊ฐ€ ์˜์†ํ™” ์ดํ›„์—๋Š” isNew๊ฐ€ false๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋„๋ก ์„ค์ •
        */
    @PostPersist // ์˜์†ํ™” ์ดํ›„ ์‹คํ–‰ ์• ๋…ธํ…Œ์ด์…˜
    @PostLoad // ์˜์†ํ™”ํ•œ ๋ฐ์ดํ„ฐ ์กฐํšŒ ์ดํ›„ ์‹คํ–‰ ์• ๋…ธํ…Œ์ด์…˜
    protected fun load() {
        _isNew = false
    }
}

User.kt

@Entity
@Table(name = "`user`")
class User(
    name: String,
) : PrimaryKeyEntity() {
    @Column(nullable = false, unique = true)
    var name: String = name
        protected set // User Entity ์ž์‹ ์ด๋‚˜ ์ƒ์† Entity์—์„œ๋งŒ ์ด๋ฆ„ ๋ณ€๊ฒฝ ๊ฐ€๋Šฅ

    @OneToMany(fetch = FetchType.LAZY, cascade = [CascadeType.ALL], mappedBy = "writer")
    protected val mutableBoards: MutableList<Board> = mutableListOf()
    val boards: List<Board> get() = mutableBoards.toList()

    fun writeBoard(board: Board) {
        mutableBoards.add(board)
    }
}

Tag.kt

@Entity
@Table(uniqueConstraints = [UniqueConstraint(name = "tag_key_value_uk", columnNames = ["`key`", "`value`"])])
class Tag(
    key: String,
    value: String,
) : PrimaryKeyEntity() {
    @Column(name = "`key`", nullable = false)
    var key: String = key
        protected set

    @Column(name = "`value`", nullable = false)
    var value: String = value
        protected set
}

Board.kt

@Entity
class Board(
    title: String,
    content: String,
    information: BoardInformation,
    writer: User,
    tags: Set<Tag>,
) : PrimaryKeyEntity() {
    @Column(nullable = false)
    var createdAt: LocalDateTime = LocalDateTime.now()
        protected set

    @Column(nullable = false)
    var title: String = title
        protected set

    @Column(nullable = false, length = 3000)
    var content: String = content
        protected set

    @Embedded
    var information: BoardInformation = information
        protected set

    @ManyToOne(fetch = FetchType.LAZY, optional = false)
    @JoinColumn(nullable = false)
    var writer: User = writer
        protected set

    @ManyToMany(fetch = FetchType.LAZY, cascade = [CascadeType.PERSIST, CascadeType.MERGE])
    @JoinTable(
        name = "board_tag_assoc",
        joinColumns = [JoinColumn(name = "board_id")],
        inverseJoinColumns = [JoinColumn(name = "tag_id")],
    )
    protected val mutableTags: MutableSet<Tag> = tags.toMutableSet()
    val tags: Set<Tag> get() = mutableTags.toSet()

    @ElementCollection
    @CollectionTable(name = "board_comment")
    private val mutableComments: MutableList<Comment> = mutableListOf()
    val comments: List<Comment> get() = mutableComments.toList()

    fun update(data: BoardUpdateData) {
        title = data.title
        content = data.content
        information = data.information
    }

    fun addTag(tag: Tag) {
        mutableTags.add(tag)
    }

    fun removeTag(tagId: UUID) {
        mutableTags.removeIf { it.id == tagId }
    }

    fun addComment(comment: Comment) {
        mutableComments.add(comment)
    }

    init {
        writer.writeBoard(this)
    }
}

@Embeddable
data class BoardInformation(
    @Column
    val link: String?,

    @Column(nullable = false)
    val rank: Int,
)

@Embeddable
data class Comment(
    @Column(length = 3000)
    val content: String,

    @ManyToOne(fetch = FetchType.LAZY, optional = false)
    @JoinColumn(nullable = false)
    val writer: User,
)
Tip

(1) allopen

์ฝ”ํ‹€๋ฆฐ์—์„œ๋Š” JPA Entity ์„ค๊ณ„์‹œ allopen, no-args constructor ์˜ต์…˜ ํ•„์š”

JPA์—์„œ๋Š” ํด๋ž˜์Šค ํ™•์žฅ ๋ฐ ํ”„๋ก์‹œ๋ฅผ ๋งŒ๋“ค๊ธฐ ์œ„ํ•ด ํด๋ž˜์Šค๋ฅผ ์ƒ์†ํ•˜๋ ค ํ•˜๋Š”๋ฐ ํด๋ž˜์Šค๊ฐ€ final class๋ผ๋ฉด ํ™•์žฅ์ด ๋ถˆ๊ฐ€๋Šฅํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒ

  • JPA ํ”Œ๋Ÿฌ๊ทธ์ธ์€ JPA ๊ด€๋ จ ํด๋ž˜์Šค ์ƒ์„ฑ์— ๋ฌธ์ œ๊ฐ€ ์—†๋„๋ก ์ƒ์„ฑ์ž ๋งค๊ฐœ๋ณ€์ˆ˜๊ฐ€ ์—†๋Š” No-arg plugin๋„ ํฌํ•จ๋˜๋Š”๋ฐ, ์ด๋Š” JPA์—์„œ ์—”ํ‹ฐํ‹ฐ ๋งคํ•‘ ๋ฐฉ์‹์ด ๋ฆฌํ”Œ๋ ‰์…˜์„ ์ด์šฉํ•œ ํ”„๋กœํผํ‹ฐ ์ฃผ์ž…์ด๊ธฐ ๋•Œ๋ฌธ

  • ๊ทธ๋ž˜์„œ ๋ณดํ†ต ์ฝ”ํ‹€๋ฆฐ์œผ๋กœ JPA ํ”„๋กœ์ ํŠธ๋ฅผ ์„ค์ •ํ•  ๋•Œ, ํ”Œ๋Ÿฌ๊ทธ์ธ์„ ์ถ”๊ฐ€ํ•ด์„œ ํด๋ž˜์Šค๋“ค์ด ์ž๋™์œผ๋กœ open๋  ์ˆ˜ ์žˆ๋„๋ก ์„ค์ • ํ•„์š”

kotlin("plugin.spring") version "1.7.0"
kotlin("plugin.jpa") version "1.7.0"

โš ๏ธ ํ•˜์ง€๋งŒ, ํ”Œ๋Ÿฌ๊ทธ์ธ์„ ์ถ”๊ฐ€ํ•˜๋”๋ผ๋„ Entity Decompile์„ ํ•ด๋ณด๋ฉด final ํ‚ค์›Œ๋“œ๊ฐ€ ์žˆ๋‹ค.

  • ๊ทธ๋ž˜์„œ Hibernate ์‚ฌ์šฉ์„ ์œ„ํ•ด Entity, Entity์˜ ์ธ์Šคํ„ด์Šค ๋ณ€์ˆ˜๋Š” final์ด ์•„๋‹ˆ์–ด์•ผ ํ•˜๋ฏ€๋กœ ์ถ”๊ฐ€ ์„ค์ •์ด ํ•„์š”

allOpen {
    annotation("javax.persistence.Entity")
    annotation("javax.persistence.MappedSuperclass")
    annotation("javax.persistence.Embeddable")
}

(2) PrimaryKeyEntity

โœ… ์—”ํ‹ฐํ‹ฐ ๊ณตํ†ต ์‹๋ณ„์ž

๋ชจ๋“  Entity๊ฐ€ PrimaryKeyEntity๋ฅผ ์ƒ์†๋ฐ›์•„ ์‚ฌ์šฉํ•˜๋„๋ก ํ•˜๋ฉด ๊ณตํ†ต๋œ PrimaryKey ๋ฅผ ์‚ฌ์šฉ

  • Integer, Long ํƒ€์ž…์€ Entity ๊ฐ„์—๋„ ํ‚ค ๊ฐ’์ด ์ค‘๋ณต๋  ์ˆ˜ ์žˆ๊ณ  MAX_VALUE์˜ ์ฐจ์ด๋„ ์žˆ์–ด์„œ UUID๋ฅผ ๋งŽ์ด ์„ ํƒํ•˜๋Š” ์ถ”์„ธ

  • ํ•˜์ง€๋งŒ, UUID ๋˜ํ•œ ์ •๋ ฌ๋ถˆ๊ฐ€๋Šฅํ•˜๋‹ค๋Š” ๋‹จ์ ๊ณผ Long๋ณด๋‹ค ํฌ๊ธฐ๋‚˜ ์ƒ์„ฑ๋น„์šฉ๋„ ํฌ๋‹ค๋Š” ๋‹จ์  ์กด์žฌ

    • ์ •๋ ฌ์€ ULID๋ฅผ ์‚ฌ์šฉํ•ด์„œ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ๊ณ 

    • ํฌ๊ธฐ๋Š” ๊ณ ๋ คํ•  ์ •๋„์˜ ์‹œ์Šคํ…œ์ด๋ผ๋ฉด ORM ์‚ฌ์šฉ ์ž์ฒด๋ฅผ ๊ณ ๋ คํ•ด ๋ณด์•„์•ผ ํ•  ๊ฒƒ์ด๋‹ค.

ULID๋Š” UUID์™€ ํ˜ธํ™˜์„ฑ์„ ๊ฐ€์ง€๋ฉฐ ์‹œ๊ฐ„์ˆœ์œผ๋กœ ์ •๋ ฌํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ํŠน์ง•

  • ULID ๊ตฌํ˜„ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋Š” ULID Creator๋ฅผ ์ฃผ๋กœ ์‚ฌ์šฉ

    • Monotinic ํ•จ์ˆ˜๋ฅผ ์ œ๊ณตํ•ด์ค˜์„œ ๊ธฐ์กด ULID๊ฐ€ ๋ฐ€๋ฆฌ์ดˆ๊นŒ์ง€๋งŒ ์ œ๊ณต๋˜๋Š”๋ฐ, ๋™์ผํ•œ ๋ฐ€๋ฆฌ์ดˆ๊ฐ€ ์žˆ์„ ๊ฒฝ์šฐ ๋‹ค์Œ ์ƒ์„ฑ ULID์˜ ๋ฐ€๋ฆฌ์ดˆ๋ฅผ 1์ฆ๊ฐ€์‹œ์ผœ ์ƒ์„ฑ

โœ… Nullable ํƒ€์ž…์„ ๋ฐฉ์ง€

๊ธฐ์กด ์ž๋ฐ”์—์„œ๋Š” JPA ์—”ํ‹ฐํ‹ฐ์˜ New ์—ฌ๋ถ€๋ฅผ ์‹๋ณ„์ž(null or 0)๋กœ ํŒ๋‹จํ•˜์ง€๋งŒ

PrimaryKeyEntity๋Š” Persistable ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๊ตฌํ˜„ํ•˜์—ฌ Persistable.isNew ํ•จ์ˆ˜ ํ™œ์šฉ

โœ… ๊ณตํ†ต ๋™์ผ์„ฑ ๋ณด์žฅ

Entity๋Š” ๊ฐ™์€ ์‹๋ณ„์ž๋ฅผ ๊ฐ€์งˆ ๊ฒฝ์šฐ ๋™์ผํ•œ ๊ฐ์ฒด๋กœ ํŒ๋‹จํ•ด์•ผ ํ•˜๊ธฐ๋ฏ€๋กœ equals, hashCode ์žฌ์ •์˜๊ฐ€ ํ•„์š”ํ•œ๋ฐ

PrimaryKeyEntity ์—์„œ equals, hashCode๋ฅผ ์žฌ์ •์˜ ํ•œ ๋’ค ๊ณตํ†ต์œผ๋กœ ์‚ฌ์šฉํ•œ๋‹ค๋ฉด ์ด๋Ÿฐ ๋ถˆํŽธํ•จ์„ ์—†์•จ ์ˆ˜ ์žˆ๋‹ค.

(3) ์ฑ„๋ฒˆ ์œ ๋ฐœ์„ ๋ฐฉ์ง€

๊ธฐ์กด JPA์—์„œ์˜ ID ์ƒ์„ฑ ๋ฐฉ๋ฒ•์€ @GeneratedValue์˜ auto_increment, sequence, sequence table ๋“ฑ์˜ ์ „๋žต์„ ์‚ฌ์šฉ

  • ํ•˜์ง€๋งŒ, ์ด ๋ฐฉ์‹์€ ๋ชจ๋‘ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์ฑ…์ž„์„ ์ „๊ฐ€ํ•˜๊ณ  ๋ถ€ํ•˜๋ฅผ ์œ ๋ฐœํ•˜์—ฌ ํŠธ๋ž˜ํ”ฝ์ด ํฐ ์„œ๋น„์Šค์—์„œ๋Š” ์ด๋Ÿฐ ์ฑ„๋ฒˆ ํ™œ๋™์ด ์ƒ๋‹นํ•œ ๋ถ€ํ•˜๋กœ ์ž‘์šฉ

ํ•ด๊ฒฐ์ฑ…์œผ๋กœ PrimaryKeyEntity ๊ฐ™์€ ํด๋ž˜์Šค๋ฅผ ์ด์šฉํ•ด Entity ์ƒ์„ฑ์‹œ Primary Key๋„ ํ•จ๊ป˜ ์ƒ์„ฑ

  • ULID๋Š” ํ•ญ์ƒ ์ค‘๋ณต๋˜์ง€ ์•Š๋Š”๋‹ค.

โš ๏ธ PrimaryKeyEntity ๋ฅผ ์ด์šฉํ•ด์„œ ์˜์†ํ™” ์ „ Key๋ฅผ ์ƒ์„ฑํ•ด์ฃผ๋Š” ๋ฐฉ์‹์˜ ์ฃผ์˜์ 

  • null์ธ ๊ฒฝ์šฐ์—๋งŒ ์ƒˆ๋กœ์šด Entity๋กœ ํŒ๋‹จํ•˜์—ฌ, ์˜์†ํ™” ๋˜์ง€์•Š์€ ์—”ํ‹ฐํ‹ฐ์— Key๊ฐ€ ํ• ๋‹น๋˜๋ฉด persist ๋˜๋Š”๊ฒŒ ์•„๋‹Œ merge ๋™์ž‘

  • ๊ทธ๋Ÿฌ๋‹ค๋ณด๋‹ˆ ํ•œ ๋ฒˆ ์กฐํšŒ ํ›„ ์ €์žฅํ•˜๋Š” ๋ถˆํ•„์š”ํ•œ ์ฟผ๋ฆฌ๋ฅผ ์‹คํ–‰

  • ๐Ÿ™†๐Ÿปโ€โ™‚๏ธ ์ด๊ฒƒ์€ Persistable ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๊ตฌํ˜„ํ•ด์„œ ๋ถˆํ•„์š”ํ•œ ์ฟผ๋ฆฌ๋ฅผ ๋ฐฉ์ง€

    • getId, isNew(์ƒˆ๋กœ์šด ์—”ํ‹ฐํ‹ฐ ํŒŒ์•…) ํ•จ์ˆ˜ ์ œ๊ณต

    • Persistable ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๊ตฌํ˜„ํ•œ Entity๋ฅผ ์˜์†ํ™” ํ•˜๋ คํ•˜๋ฉด

    • JpaPersistableEntityInformation.isNew ํ•จ์ˆ˜๊ฐ€ ํ˜ธ์ถœ๋˜๋ฉฐ ๋‚ด๋ถ€์ ์œผ๋กœ Persistable.isNew ํ•จ์ˆ˜ ํ˜ธ์ถœ

(4) Property ์ ‘๊ทผ ์ œ์–ด

โœ… ํ”„๋กœํผํ‹ฐ์˜ ๋ณ€๊ฒฝ์„ ์ตœ์†Œํ™”

protected set์„ ํ†ตํ•œ setter์˜ ์ ‘๊ทผ ์ œํ•œ

Entity ์ž์‹ ์ด๋‚˜ ์ƒ์† Entity์—์„œ๋งŒ ์ด๋ฆ„ ๋ณ€๊ฒฝ ๊ฐ€๋Šฅ

  • Entity์— ๋Œ€ํ•ด allOpen ์˜ต์…˜์„ ์ถ”๊ฐ€ํ–ˆ๊ธฐ ๋•Œ๋ฌธ์—, open property์˜ ๊ฒฝ์šฐ private setter ๋ฏธํ—ˆ์šฉ

@Entity
@Table(name = "`user`")
class User(
    name: String,
) : PrimaryKeyEntity() {
    @Column(nullable = false, unique = true)
    var name: String = name
        protected set
}

โœ… ์ƒ์„ฑ์ผ, ์ˆ˜์ •์ผ๊ณผ ๊ฐ™์ด ๋ณ€๊ฒฝ์ด ํ•„์š” ์—†๋Š” ํ”„๋กœํผํ‹ฐ

  • ๋‹ค๋ฅธ ํ”„๋กœํผํ‹ฐ์ฒ˜๋Ÿผ setter์˜ ์ ‘๊ทผ์ œ์–ด๋ฅผ protected๋กœ ์„ ์–ธ

  • ๋‚ด๋ถ€์ ์œผ๋กœ๋งŒ ๋ณ€๊ฒฝ์„ ์—ด์–ด๋’€๊ธฐ์— ์ง์ ‘ ๊ฐ์ฒด ๋‚ด๋ถ€์—์„œ ๋ณ€๊ฒฝ์„ ํ•˜์ง€ ์•Š๋Š” ํ•œ ์•ˆ์ „

  • ๊ฐ์ฒด ์ž์ฒด์—์„œ ๋ณ€๊ฒฝ์„ ์‹œ๋„ํ•  ์ˆ˜ ์žˆ์ง€๋งŒ, ๋ถˆ๋ณ€ ํ”„๋กœํผํ‹ฐ(immutable)๋„ ๊ฐœ๋ฐœ์ž๊ฐ€ ๋ณ€๊ฒฝ ํ”„๋กœํผํ‹ฐ(mutable)๋กœ ๋ฐ”๊ฟ€ ์ˆ˜ ์žˆ๋Š” ๊ฒƒ์€ ๋™์ผํ•˜๋‹ค๊ณ  ํŒ๋‹จ

@Entity
@Table(name = "`user`")
class User(
    name: String,
) : PrimaryKeyEntity() {
    @Column(nullable = false, unique = true)
    var name: String = name
        protected set
    
    @Column(nullable = false)
    var createdAt: LocalDateTime = LocalDateTime.now()
        protected set
}

(5) nullable

๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์™€ Entity์˜ ์Šคํ‚ค๋งˆ๊ฐ€ ๋ถˆ์ผ์น˜ ํ•˜๋Š” ๊ฒฝ์šฐ ํƒ€์ž…์ด ๋‹ค๋ฅธ๊ฑด JPA์—์„œ ์žก์•„์„œ ๋นŒ๋“œ ์‹œ์ ์— ์•Œ๋ ค์ค„ ์ˆ˜ ์žˆ์ง€๋งŒ,

  • nullableํ•œ Column์„ Entity์— non-nullableํ•˜๊ฒŒ ์„ ์–ธํ•œ ๊ฒฝ์šฐ Column์— ๊ฐ’์ด null์ด๋ผ๋ฉด ๋Ÿฐํƒ€์ž„ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒ

์ด ๊ฒฝ์šฐ @Column ์• ๋…ธํ…Œ์ด์…˜์˜ nullable ์†์„ฑ์„ ๋ช…์‹œํ•˜์—ฌ ํ”„๋กœํผํ‹ฐ์˜ ์†์„ฑ ํƒ€์ž…์„ ์•Œ๋ ค์ฃผ๋ฉด, ์œ„์™€ ๊ฐ™์€ ๋Ÿฐํƒ€์ž„ ์˜ค๋ฅ˜๋ฅผ ๋ง‰์•„์ฃผ๊ณ , ํŒŒ์•…ํ•˜๊ธฐ ์‰ฌ์›Œ์งˆ ์ˆ˜ ์žˆ๋‹ค

@Column(nullable = false)
var title: String = title
    protected set

(6) ์™ธ๋ถ€์— ๋…ธ์ถœํ•˜๋Š” ์—ฐ๊ด€๊ด€๊ณ„๋Š” Immutable Collection์„ ๋…ธ์ถœ

JPA์—์„œ ์—ฐ๊ด€๊ด€๊ณ„์˜ ์š”์†Œ ๋ณ€๊ฒฝ์€ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์˜ ๋ณ€๊ฒฝ์„ ์œ ๋ฐœํ•˜์—ฌ ํ”„๋กœํผํ‹ฐ๋ฅผ ๋ถˆ๋ณ€(val)์œผ๋กœ ์„ ์–ธํ•ด๋„ ์œ„/๋ณ€์กฐ๊ฐ€ ๊ฐ€๋Šฅ

// ์œ„๋ณ€๊ฐ€ ๊ฐ€๋Šฅํ•œ MutableList
@OneToMany(fetch = FetchType.LAZY, cascade = [CascadeType.ALL], mappedBy = "writer")
val mutableBoards: MutableList<Board> = mutableListOf()

ํ•˜์ง€๋งŒ, ์—ฐ๊ด€๊ด€๊ณ„๋ฅผ ๋ณ€๊ฒฝํ•˜๋Š”๊ฒƒ์€ Entity์˜ ํŠน์„ฑ์ƒ ํ•„์š”ํ•˜๊ธฐ ๋•Œ๋ฌธ์— MutableList๋ฅผ List๋กœ ๋ฐ”๊ฟ€ ์ˆœ ์—†๋‹ค. ๊ทธ๋ ‡๊ธฐ์— ๋‚ด๋ถ€ ์กฐ์ž‘์šฉ ํ”„๋กœํผํ‹ฐ์™€ ์™ธ๋ถ€ ๋…ธ์ถœ์šฉ ํ”„๋กœํผํ‹ฐ๋ฅผ ๋ณ„๋„๋กœ ๋‘์–ด ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ๋‹ค.

@OneToMany(fetch = FetchType.LAZY, cascade = [CascadeType.ALL], mappedBy = "writer")
protected val mutableBoards: MutableList<Board> = mutableListOf()
val boards: List<Board> get() = mutableBoards // ์™ธ๋ถ€ ๋…ธ์ถœ์šฉ ํ”„๋กœํผํ‹ฐ

boards๋ฅผ ์กฐํšŒํ•˜๋Š” ์‹œ์ ์— ์ฐธ์กฐ ์ฃผ์†Œ๊ฐ’์„ ๊ฐ€์ง€๊ณ  ์žˆ์œผ๋ฏ€๋กœ ์ดํ›„ ๊ฒŒ์‹œํŒ์„ ์ถ”๊ฐ€ํ–ˆ์„๋•Œ ๋‚ด์šฉ์ด ์—ฐ๋™

@OneToMany(fetch = FetchType.LAZY, cascade = [CascadeType.ALL], mappedBy = "writer")
protected val mutableBoards: MutableList<Board> = mutableListOf()
val boards: List<Board> get() = mutableBoards.toList()

// boards์— ์˜ํ–ฅ์„ ๋ผ์น˜์ง€ ์•Š๋„๋ก ์ถ”๊ฐ€
fun writeBoard(board: Board) {
    mutableBoards.add(board)
}

Kotest

์ฝ”ํ‹€๋ฆฐ์˜ ํ…Œ์ŠคํŠธ๋„๊ตฌ Kotest

Junit๊ณผ Kotest์˜ ์ฐจ์ด์  ์ค‘ ๊ฐ€์žฅ ํฐ ๋ถ€๋ถ„์€ ๊ฐ„๊ฒฐํ•จ

JUnit 5 vs Kotest. Part 1: Is it the new way we test?

class RacingServiceTest : BehaviorSpec({
    Given("์œ ํšจํ•œ ํšŸ์ˆ˜์™€ ์ž๋™์ฐจ ์ฐธ๊ฐ€์ž๊ฐ€ ์ œ๊ณต๋œ๋‹ค.") {
        val info = RacingGameRequest(numberOfRound = 5, carNames = listOf("a", "b", "c", "d", "e"))

        And("์ „์ง„ ์ „๋žต์ด ํ•ญ์ƒ ์ „์ง„์„ ๋ฐ˜ํ™˜ํ•œ๋‹ค.") {
            val racingService = RacingService { DirectionType.STRAIGHT }

            When("๊ฒฝ์ฃผ๋ฅผ ์ง„ํ–‰ ํ–ˆ์„ ๊ฒฝ์šฐ") {
                val actual = racingService.racing(info)

                Then("์ „๋‹ฌ๋ฐ›์€ ์ž๋™์ฐจ ๋Œ€์ˆ˜๋งŒํผ ์ž๋™์ฐจ๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ๊ฒฐ๊ณผ๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค.") {
                    actual.racingHistories shouldHaveSize 5
                    actual.racingHistories.mapIndexed { idx, info ->
                        info.records.forEach {
                            it.value shouldBe Distance(idx + 1L)
                        }
                    }
                    actual.winners shouldBe arrayOf(Name("a"), Name("b"), Name("c"), Name("d"), Name("e"))
                }
            }
        }
    }
})

Get Start

build.gradle.kts

tasks.withType<Test>().configureEach {
   useJUnitPlatform()
}

...

val version = "5.4.0"

testImplementation 'io.kotest:kotest-runner-junit5:$version'

// ๊ฒ€์ฆ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ
testImplementation 'io.kotest:kotest-assertions-core:$version'
testImplementation 'io.kotest:kotest-property:$version'

IntelliJ ํ”Œ๋Ÿฌ๊ทธ์ธ

  • Preference โ†’ Plugins โ†’ Kotest

Kotest ์žฅ์ ๋“ค

โœ… ์œ ์—ฐ์„ฑ

  • ๋‹ค์–‘ํ•œ ํ…Œ์ŠคํŠธ ์Šคํƒ€์ผ์„ ์ง€์›

  • ํ–‰์œ„ ์ฃผ๋„ ํ…Œ์ŠคํŠธ(BDD)๋ฟ ์•„๋‹ˆ๋ผ WordSpec, FunSpec, AnnotationSpec, FreeSpec ๋“ฑ ๋‹ค์–‘ํ•œ ์Šคํƒ€์ผ ์ง€์›

โœ… ๊ฐ•๋ ฅํ•˜๊ณ  ๋‹ค์–‘ํ•œ ๊ฒ€์ฆ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ

  • ๋ณต์žกํ•œ ํ‘œํ˜„์‹์ด๋‚˜, ์ปฌ๋ ‰์…˜, ์˜ˆ์™ธ ๋“ฑ์„ ๊ฒ€์ฆํ•˜๋Š”๋ฐ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๊ฒ€์ฆ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ(assertion) ์ œ๊ณต

โœ… ํ”„๋กœํผํ‹ฐ ๊ธฐ๋ฐ˜ ํ…Œ์ŠคํŠธ

  • ํ”„๋กœํผํ‹ฐ ๊ธฐ๋ฐ˜ ํ…Œ์ŠคํŠธ๋ฅผ ์ง€์›

  • ์ž„์˜์˜ ์ž…๋ ฅ๊ฐ’์„ ๋งŒ๋“ค์–ด ์ฝ”๋“œ์˜ ์œ ํšจ์„ฑ์„ ๊ฒ€์‚ฌํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ ๋‹ค์–‘ํ•œ ๊ฒฝ์šฐ์˜ ์ˆ˜๋ฅผ ์ฒด๊ณ„์ ์œผ๋กœ ํ…Œ์ŠคํŠธ

class MyTests : PropertySpec({
  forAll { a: Int, b: Int ->
    (a + b) should beGreaterThan(a)
    (a + b) should beGreaterThan(b)
  }
})

โœ… ๋ฐ˜๋ณต ๋ฐ ์ค‘์ฒฉ ํ…Œ์ŠคํŠธ

  • ๋ฐ˜๋ณต ๋ฐ ์ค‘์ฒฉ ํ…Œ์ŠคํŠธ๋ฅผ ์ง€์›ํ•˜์—ฌ ์—ฌ๋Ÿฌ ๋ณต์žกํ•œ ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค๋ฅผ ๋” ์‰ฝ๊ณ  ๊ฐ„๊ฒฐํ•˜๊ฒŒ ๊ด€๋ฆฌ

class MyTests : FunSpec({
  context("Some context") {
    test("Test 1") { /*...*/ }
    test("Test 2") { /*...*/ }
  }
})

Testing Style

https://kotest.io/docs/framework/testing-styles.html

โœ… StringSpec

class StringTest: StringSpec({
		// ๋ฌธ์ž์—ด์€ JUnit์˜ @DisplayName์„ ๋Œ€์ฒด
		"strings.length should return size of string" {
	            "hello".length shouldBe 5
	  }
})

โœ… FunSpec

class FunSpecTest: FunSpec ({
    test("๋ฌธ์ž์—ด ๊ธธ์ด ํ…Œ์ŠคํŠธ") {
        val actual = "abcdefg   "
      
        actual.length shouldBe 10
    }
})

โœ… AnnotationSpec

  • JUnit ํ…Œ์ŠคํŠธ ๋ฐฉ์‹๊ณผ ์œ ์‚ฌ

class AnnotationSpecTest: AnnotationSpec () {
    @BeforeEach
    fun beforeEach() {
        println("start beforeEach")
    }

    @Test
    fun stringTest() {
        val actual = "abcdefg   "

        actual.length shouldBe 10
    }
}

โœ… DescribeSpec

  • describe๊ฐ€ ํ…Œ์ŠคํŠธ ๋Œ€์ƒ์„ ์ง€์นญ

  • ๋‚ด๋ถ€์ ์œผ๋กœ ์กฐ๊ฑด์ด๋‚˜ ์ƒํ™ฉ์„ ์„ค๋ช…ํ•  ๋•Œ๋Š” context ์‚ฌ์šฉ

  • ํ…Œ์ŠคํŠธ ๋ณธ์ฒด์—๋Š” it ์„ ์‚ฌ์šฉํ•ด์„œ ํ…Œ์ŠคํŠธ ์Šคํ† ๋ฆฌํ…”๋ง

class MyTests : DescribeSpec({
    describe("score") { // ํ…Œ์ŠคํŠธ ๋Œ€์ƒ
        it("start as zero") {
            // test here
        }
        describe("with a strike") { // ๋‚ด๋ถ€ ์กฐ๊ฑด์ด๋‚˜ ์ƒํ™ฉ
            it("adds ten") {
                // test here
            }
            it("carries strike to the next frame") {
                // test here
            }
        }

        describe("for the opposite team") {
            it("Should negate one score") {
                // test here
            }
        }
    }
})
  • ๋งŒ์ผ ํ…Œ์ŠคํŠธ ๋Œ€์ƒ disabled๋ฅผ ์ ์šฉํ•˜๊ณ  ์‹ถ์„ ๊ฒฝ์šฐ xdescribe ์‚ฌ์šฉ

class MyTests : DescribeSpec({
    describe("this outer block is enabled") {
        xit("this test is disabled") {
            // test here
        }
    }
    xdescribe("this block is disabled") {
        it("disabled by inheritance from the parent") {
            // test here
        }
    }
})

โœ… BehaviorSpec

  • ํ–‰์œ„ ์ฃผ๋„ ํ…Œ์ŠคํŠธ ๋ฐฉ์‹

  • JUnit Nested ์• ๋…ธํ…Œ์ด์…˜์„ ํ™œ์šฉํ•œ ๊ณ„์ธต ๊ตฌ์กฐ ํ…Œ์ŠคํŠธ ๋ฐฉ์‹๊ณผ ์œ ์‚ฌํ•˜์ง€๋งŒ ๋” ํŽธํ•˜๊ฒŒ ์‚ฌ์šฉ ๊ฐ€๋Šฅ

class NameTest : BehaviorSpec({
    Given("Name ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•  ๋•Œ") {
        When("5๊ธ€์ž ์ด๋‚ด์˜ ๋ฌธ์ž์—ด์„ ์ „๋‹ฌํ•˜๋ฉด") {
            val actual = Name("12345")

            Then("์ •์ƒ์ ์œผ๋กœ ์ƒ์„ฑ๋œ๋‹ค") {
                actual shouldBe Name("12345")
            }
        }

        When("5๊ธ€์ž ์ด์ƒ์˜ ๋ฌธ์ž์—ด์„ ์ „๋‹ฌํ•˜๋ฉด") {
            Then("์˜ˆ์™ธ๋ฅผ ๋˜์ง„๋‹ค") {
                assertThrows<IllegalArgumentException> {
                    Name("123456")
                }
            }
        }
    }
})

Assertion

https://kotest.io/docs/assertions/assertions.html

๊ฒ€์ฆ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์—์„œ ์ œ๊ณตํ•˜๋Š” ์—ฌ๋Ÿฌ ๊ฒ€์ฆ ํ•จ์ˆ˜

class MatcherTest : StringSpec() {
    init {
        -----------------------String Matchers -------------------------
        // 'shouldBe'๋Š” ๋™์ผํ•จ์„ ์ฒดํฌํ•˜๋Š” Matcher
        "hello world" shouldBe haveLength(11) // length๊ฐ€ 11์ด์–ด์•ผ ํ•จ์„ ์ฒดํฌ
        "hello" should include("ll") // ํŒŒ๋ผ๋ฏธํ„ฐ๊ฐ€ ํฌํ•จ๋˜์–ด ์žˆ๋Š”์ง€ ์ฒดํฌ
        "hello" should endWith("lo") // ํŒŒ๋ผ๋ฏธํ„ฐ๊ฐ€ ๋์˜ ํฌํ•จ๋˜๋Š”์ง€ ์ฒดํฌ
        "hello" should match("he...") // ํŒŒ๋ผ๋ฏธํ„ฐ๊ฐ€ ๋งค์นญ๋˜๋Š”์ง€ ์ฒดํฌ
        "hello".shouldBeLowerCase() // ์†Œ๋ฌธ์ž๋กœ ์ž‘์„ฑ๋˜์—ˆ๋Š”์ง€ ์ฒดํฌ

        -----------------------Collection Matchers -------------------------
        val list = emptyList<String>()
        val list2 = listOf("a", "b", "c")
        val map = mapOf<String, String>(Pair("a", "1"))

        list should beEmpty() // ์›์†Œ๊ฐ€ ๋น„์—ˆ๋Š”์ง€ ์ฒดํฌ ํ•ฉ๋‹ˆ๋‹ค.
        list2 shouldBe sorted<String>() // ํ•ด๋‹น ์ž๋ฃŒํ˜•์ด ์ •๋ ฌ ๋˜์—ˆ๋Š”์ง€ ์ฒดํฌ
        map should contain("a", "1") // ํ•ด๋‹น ์›์†Œ๊ฐ€ ํฌํ•จ๋˜์—ˆ๋Š”์ง€ ์ฒดํฌ
        map should haveKey("a") // ํ•ด๋‹น key๊ฐ€ ํฌํ•จ๋˜์—ˆ๋Š”์ง€ ์ฒดํฌ
        map should haveValue("1") // ํ•ด๋‹น value๊ฐ€ ํฌํ•จ๋˜์—ˆ๋Š”์ง€ ์ฒดํฌ

				
    }
}

์ฝ”ํ‹€๋ฆฐ ๊ธฐ์ดˆ, ๋‹จ์œ„ ํ…Œ์ŠคํŠธ

์ฝ”ํ‹€๋ฆฐ์€ ์ž๋ฐ” ํ”Œ๋žซํผ์—์„œ ๋™์ž‘ํ•˜๋Š” ์ƒˆ๋กœ์šด ํ”„๋กœ๊ทธ๋ž˜๋ฐ ์–ธ์–ด.

๊ทธ๋ฆฌ๊ณ  ์ •์  ํƒ€์ž… ์ง€์ • ์–ธ์–ด

Person class

/**
 *  Java
 */
public class Person {
    private final String name;
    private final int age;
    private String nickname;

    public Person(final String name, final int age, final String nickname) {
        this.name = name;
        this.age = age;
        this.nickname = nickname;
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }

    public String getNickname() {
        return nickname;
    }

    public void setNickname(final String nickname) {
        this.nickname = nickname;
    }
}

/**
 *  Kotlin
 */
class Person(val name: String, val age: Int, var nickname: String)

.

Person Test

@Test
fun `์ด๋ฆ„ ๋ถ™์ธ ์ธ์ž`() {
    // ํด๋ž˜์Šค์˜ ์†์„ฑ๋“ค์„ ์„ ์–ธํ•จ๊ณผ ๋™์‹œ์— ์ƒ์„ฑ์ž๋ฅผ ์„ ์–ธ
    class Person(val name: String, val age: Int, var nickname: String)

    val person = Person("aaron", 30, "park")
    assertEquals("aaron", person.name)
    assertEquals(30, person.age)
    assertEquals("park", person.nickname)

    person.nickname = "new nickname"
    assertEquals("new nickname", person.nickname)
}

@Test
fun `๋„ ํƒ€์ž…`() {
    // nickname ํ•„๋“œ๋Š” nallable
    class Person(val name: String, val age: Int, var nickname: String?)

    val person = Person("aaron", 30, null)
    assertEquals("aaron", person.name)
    assertEquals(30, person.age)
    assertEquals(null, person.nickname)
}

@Test
fun `๋ฐ์ดํ„ฐ ํด๋ž˜์Šค`() {
    // ๋ฐ์ดํ„ฐ ํด๋ž˜์Šค๋Š” equals, hashcode, toString, copy ๊ธฐ๋Šฅ์„ ์ œ๊ณต
    data class Person(val name: String, val age: Int, var nickname: String)

    val person1 = Person("aaron", 30, "park")
    val person2 = Person("aaron", 30, "park")
    assertEquals(person1, person2)

    val person3 = person1.copy(nickname = "kim")
    assertEquals("aaron", person3.name)
    assertEquals(30, person3.age)
    assertEquals("kim", person3.nickname)
}

TDD

ํ…Œ์ŠคํŠธ ์ฃผ๋„ ๊ฐœ๋ฐœ(Test-Driven development, TDD)

์งง์€ ๊ฐœ๋ฐœ ์‚ฌ์ดํด์„ ๋ฐ˜๋ณตํ•˜๋Š” SW ๊ฐœ๋ฐœ ํ”„๋กœ์„ธ์Šค ์ค‘ ํ•˜๋‚˜

aka. TFD(Test First Development) + ๋ฆฌํŒฉํ„ฐ๋ง

TDD์˜ ํ”„๋กœ๊ทธ๋ž˜๋ฐ ์ˆœ์„œ

[Red] Write a failing test
 โžก๏ธ [Green] Make the test pass
 โžก๏ธ [Blue] Refactor
 ๐Ÿ”
  1. [Red] : ์‹คํŒจํ•˜๋Š” ์ž‘์€ ํ…Œ์ŠคํŠธ ์ž‘์„ฑ(์ปดํŒŒ์ผ ์—๋Ÿฌ ๋ฌด์‹œ)

  2. [Green] : ํ…Œ์ŠคํŠธ๊ฐ€ ํ†ต๊ณผ๋˜๋„๋ก ์ˆ˜์ •. ์–ด๋– ํ•œ ํด๋ฆฐ์ฝ”๋“œ๋„ ๊ณ ๋ คํ•˜์ง€ ์•Š์Œ

  3. [Blue] : Green ๊ณผ์ •์—์„œ ๋ฐœ์ƒํ•œ ์—…๋ณด๋“ค์„ ๋ชจ๋‘ ์ฒญ์‚ฐ. ์—ฌ๊ธฐ์„œ ํด๋ฆฐ์ฝ”๋“œ๋ฅผ ์ง„ํ–‰

.

Why TDD โ“

(1) ๊ธฐ๋Šฅ์„ ์›์ž์ ์œผ๋กœ ๋งŒ๋“ค๊ฒŒ ๋œ๋‹ค.

  • ๋‹จ์œ„ ํ…Œ์ŠคํŠธ๋Š” ํ”„๋กœ๊ทธ๋ž˜๋ฐ์—์„œ ๊ฐ€์žฅ ์ž‘์€ ๋‹จ์œ„๋ฅผ ๊ฐ€์ง€๋‹ค ๋ณด๋‹ˆ ํ…Œ์ŠคํŠธ์˜ ๋ฒ”์œ„๋„ ์ข๋‹ค.

  • ํ•˜๋‚˜์˜ ๋ฉ”์„œ๋“œ๊ฐ€ ์—ฌ๋Ÿฌ ๋™์ž‘์„ ์ˆ˜ํ–‰ํ•˜์ง€ ์•Š๊ฒŒ ๋  ํ™•๋ฅ ์ด ๋†’์•„์ง„๋‹ค.

(2) ์ œ์–ดํ•  ์ˆ˜ ์žˆ๋Š” ๋ฒ”์œ„๋ฅผ ๋Š˜๋ฆด ์ˆ˜ ์žˆ๋‹ค.

  • ๊ธฐ๋Šฅ ๋ช…์„ธ๋ฅผ ์ƒ์„ธํ•˜๊ฒŒ ํ•˜๊ณ  ๋‹จ์œ„ ํ…Œ์ŠคํŠธ๋ฅผ ์ž‘์„ฑํ•˜๋ฉด์„œ ํ…Œ์ŠคํŠธ๊ฐ€ ์„ฑ๊ณตํ•˜๋„๋ก ๋งŒ๋“ค๊ธฐ ์œ„ํ•ด์„œ๋Š” ๋ฉ”์„œ๋“œ๊ฐ€ ๋‚ด๊ฐ€ ์ œ์–ดํ•  ์ˆ˜ ์žˆ๋„๋ก ๋งŒ๋“ค์–ด์•ผ ํ•˜๋Š”๋ฐ

  • ์ด ๊ณผ์ •์„ ํ†ตํ•ด ๋‚ด๊ฐ€ ์ œ์–ดํ•  ์ˆ˜ ์—†๋Š” ์˜์—ญ์„ ์ตœ์†Œํ™”ํ•  ์ˆ˜ ์žˆ๋‹ค.

(3) ์œ ์ง€๋ณด์ˆ˜์„ฑ์„ ๋†’์ผ ์ˆ˜ ์žˆ๋‹ค.

  • ํ”„๋กœ์ ํŠธ๊ฐ€ 5๋…„ ์ด์ƒ ์œ ์ง€๊ฐ€ ๋˜๊ณ , ๊ด€๋ จ ์†Œ์Šค์ฝ”๋“œ๋งŒ ์ˆ˜๋ฐฑ ๊ฐœ๊ฐ€ ๋„˜์–ด๊ฐ€๋Š” ์ƒํ™ฉ์ด ๋˜์—ˆ์„ ๋•Œ ํ•˜๋‚˜์˜ ํŽ˜์ด์ง€๋ฅผ ๊ตฌ์„ฑํ•˜๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉ๋˜๋Š” ํด๋ž˜์Šค๊ฐ€ ์ˆ˜์‹ญ ๊ฐœ๊ฐ€ ๋„˜์„ ๊ฒƒ์ด๋‹ค.

  • ์ˆ˜์‹ญ/์ˆ˜๋ฐฑ ๊ฐœ์ค‘ ํ•˜๋‚˜์˜ ํด๋ž˜์Šค์˜ ํŠน์ • ๋ฉ”์„œ๋“œ๊ฐ€ ๋ฌธ์ œ๊ฐ€ ์ƒ๊ฒผ๋‹ค๊ณ  ํ•  ๋•Œ ์ด ์—๋Ÿฌ๋ฅผ ์ฐพ๋Š” ์‹œ๊ฐ„์ด ์งง์„ ๋• ๋ช‡ ๋ถ„์ผ ์ˆ˜ ์žˆ์ง€๋งŒ ๊ธธ ๋•Œ๋Š” ๋ฉฐ์น ๋กœ๋„ ๋ถ€์กฑํ•  ์ˆ˜ ์žˆ๋‹ค.

  • ์ด๋•Œ ํ•ด๋‹น ํด๋ž˜์Šค์— ๋Œ€ํ•œ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๊ฐ€ ์ž‘์„ฑ๋˜์–ด ์žˆ๋‹ค๋ฉด, ๋ฐฐํฌ ์ „ ํ…Œ์ŠคํŠธ ๋นŒ๋“œ ์‚ฌ์ดํด์„ ํ†ตํ•ด ์ฐพ์•„๋‚ผ ์ˆ˜ ์žˆ๋‹ค.


FP, ์ฝ”ํ‹€๋ฆฐ DSL

ํ•จ์ˆ˜ํ˜• ํ”„๋กœ๊ทธ๋ž˜๋ฐ

Why FPโ“

๋™์‹œ์„ฑ ์ด์Šˆ

  • ๋ฐ์ดํ„ฐ์˜ ์ƒํƒœ๋ฅผ ๋ณ€๊ฒฝํ•˜๋Š” ๊ฐ์ฒด ์ง€ํ–ฅ ํ”„๋กœ๊ทธ๋ž˜๋ฐ ๋ฐฉ์‹์œผ๋กœ ๋™์‹œ์„ฑ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๋Š” ๋ฐ๋Š” ํ•œ๊ณ„ ์กด์žฌ

๋ฐ์ดํ„ฐ ๊ด€๋ฆฌ์— ๋”ฐ๋ฅธ ๋ถ€๋‹ด

  • ๋Œ€์šฉ๋Ÿ‰ ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ ์‹œ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ์ฒด๋กœ ๋ณ€ํ™˜ํ•˜๋Š”๋ฐ ํฐ ๋ถ€๋‹ด

  • ๋Œ€์šฉ๋Ÿ‰ ๋ฐ์ดํ„ฐ๋ฅผ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋Š” ํšจ์ธŒ์ ์ธ ๋ฐ์ดํ„ฐ ๊ตฌ์กฐ์™€ ๋ฐ์ดํ„ฐ ์—ฐ์‚ฐ์ด ํ•„์š”

ํ•จ์ˆ˜ํ˜• ํ”„๋กœ๊ทธ๋ž˜๋ฐ์˜ ๋ชจ๋“ˆํ™”

  • ํ•จ์ˆ˜ํ˜• ํ”„๋กœ๊ทธ๋ž˜๋ฐ์˜ ํ•จ์ˆ˜๋ฅผ ๋ชจ๋“ˆํ™”ํ•  ๊ฒฝ์šฐ ์ˆ˜ ๋งŽ์€ ๊ณณ์—์„œ ์žฌ์‚ฌ์šฉ ๊ฐ€๋Šฅ

  • ๋” ์œ ์šฉํ•˜๊ณ , ์žฌ์‚ฌ์šฉ์ด ํŽธ๋ฆฌํ•˜๊ณ , ๊ตฌ์„ฑ์ด ์šฉ์ดํ•˜๊ณ , ํ…Œ์ŠคํŠธํ•˜๊ธฐ ๋” ๊ฐ„ํŽธํ•œ ์ถ”์ƒํ™”๋ฅผ ์ œ๊ณต

๋” ๋น ๋ฅด๊ฒŒ ์ž‘์—…ํ•ด์•ผ ํ•œ๋‹ค.

  • ๊ฐ์ฒด์— ๋Œ€ํ•œ ๋ชจ๋ธ๋ง์— ๋งŽ์€ ์‹œ๊ฐ„์„ ํˆฌ์žํ•˜๊ธฐ๋ณด๋‹ค ์‚ฌ์šฉ์ž ์š”๊ตฌ ์‚ฌํ•ญ์— ๋Œ€ํ•ด์„œ ์ตœ์†Œํ•œ์œผ๋กœ ์ถฉ๋ถ„ํ•œ ์ˆ˜์ค€์„ ์œ ์ง€ํ•˜๋ฉด์„œ

  • ๋™์‹œ์— ๋ณ€ํ™”์— ๋Œ€ํ•ด์„œ๋„ ์œ ์—ฐํ•˜๊ฒŒ ๋Œ€์‘ํ•˜๋Š”๋ฐ ๋„์›€

๋‹จ์ˆœํ•จ

  • ํ•จ์ˆ˜ํ˜• ํ”„๋กœ๊ทธ๋ž˜๋ฐ ๋ฐฉ์‹์„ ํ†ตํ•ด ํ”„๋กœ๊ทธ๋ž˜๋ฐ ์Šคํƒ€์ผ์„ ๊ฐœ์„ ํ•ด ๋” ๊น”๋”ํ•œ ์ฝ”๋“œ๋กœ ๊ตฌํ˜„

๋ช…๋ นํ˜• ํ”„๋กœ๊ทธ๋ž˜๋ฐ vs ์„ ์–ธํ˜• ํ”„๋กœ๊ทธ๋ž˜๋ฐ

๋ช…๋ นํ˜• ํ”„๋กœ๊ทธ๋ž˜๋ฐ

  • ํ”„๋กœ๊ทธ๋ž˜๋ฐ์˜ ์ƒํƒœ์™€ ์ƒํƒœ๋ฅผ ๋ณ€๊ฒฝ์‹œํ‚ค๋Š” ๊ตฌ๋ฌธ์˜ ๊ด€์ ์œผ๋กœ ์ ‘๊ทผํ•˜๋Š” ํ”„๋กœ๊ทธ๋ž˜๋ฐ ๋ฐฉ์‹.

  • ์ปดํ“จํ„ฐ๊ฐ€ ์‹คํ–‰ํ•  ๋ช…๋ น๋“ค์„ ์‹คํ–‰ ์ˆœ์„œ๋Œ€๋กœ ๊ตฌํ˜„

    • ๋Œ€๋ถ€๋ถ„์˜ ๊ฐ์ฒด ์ง€ํ–ฅ ํ”„๋กœ๊ทธ๋ž˜๋ฐ ์–ธ์–ด๊ฐ€ ๋ช…๋ นํ˜• ํ”„๋กœ๊ทธ๋ž˜๋ฐ ์–ธ์–ด

    • ์•Œ๊ณ ๋ฆฌ์ฆ˜ ์ฒ˜๋ฆฌ ์ž‘์—…์— ์ ํ•ฉํ•œ ์–ธ์–ด

์„ ์–ธํ˜• ํ”„๋กœ๊ทธ๋ž˜๋ฐ

  • ์„ ์–ธ์œผ๋กœ๋งŒ ํ”„๋กœ๊ทธ๋žจ์„ ๋™์ž‘์‹œํ‚ค๋Š” ๋ฐฉ์‹

  • ํ”„๋กœ๊ทธ๋žจ์„ ์‹คํ–‰ํ•˜๊ธฐ ์œ„ํ•ด ๊ตฌ์ฒด์ ์ธ ์ž‘๋™ ์ˆœ์„œ๋ฅผ ๋‚˜์—ดํ•˜์ง€ ์•Š์Œ

  • ์™„์ „ํ•˜์ง€ ์•Š์ง€๋งŒ ํ•จ์ˆ˜ํ˜• ํ”„๋กœ๊ทธ๋ž˜๋ฐ์„ ํ™œ์šฉํ•ด ์ผ์ • ์ˆ˜์ค€์˜ ์„ ์–ธํ˜• ํ”„๋กœ๊ทธ๋ž˜๋ฐ ๊ฐ€๋Šฅ

    • ํ•จ์ˆ˜ํ˜• ํ”„๋กœ๊ทธ๋ž˜๋ฐ์€ ์„ ์–ธํ˜• ํ”„๋กœ๊ทธ๋ž˜๋ฐ์˜ ํ•œ ์ข…๋ฅ˜

/**
 * ๋ช…๋ นํ˜• ํ”„๋กœ๊ทธ๋ž˜๋ฐ ์Šคํƒ€์ผ
 */
fun getPoint(customer:Customer): Int {
    for (i in 0..customers.size) {
        val c = customers[i]
        if (customer == c) {
            return c.point
        }
    }
    return NO_DATA
}

/**
 * ์„ ์–ธํ˜• ํ”„๋กœ๊ทธ๋ž˜๋ฐ ์Šคํƒ€์ผ
 */
fun getPoint(customer:Customer): Int {
    if (isRegisteredCustomer(customer)) {
        return findCustomer(customer).point
    }
    return NO_DATA
}

ํ•จ์ˆ˜ํ˜• ํ”„๋กœ๊ทธ๋ž˜๋ฐ์˜ ํŠน์ง•

  • ์ž‘์—…์„ ์–ด๋–ป๊ฒŒ ์ˆ˜ํ–‰ํ•  ๊ฒƒ์ธ์ง€, How์— ์ง‘์ค‘

  • ๊ตฌ์ฒด์ ์ธ ์ž‘์—… ๋ฐฉ์‹์€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๊ฐ€ ๊ฒฐ์ •ํ•˜๊ณ , ๋ฌด์—‡(What)์„ ์ˆ˜ํ–‰ํ•  ๊ฒƒ์ธ์ง€์— ์ง‘์ค‘

  • side-effect๊ฐ€ ๋ฐœ์ƒํ•˜์ง€ ์•Š์Œ

๋ณ€๊ฒฝ ๋ถˆ๊ฐ€๋Šฅํ•œ ๊ฐ’์„ ํ™œ์šฉ

  • ๊ฐ’์ด ๋ณ€๊ฒฝ๋˜๋Š” ๊ฒƒ์„ ํ—ˆ์šฉํ•˜๋ฉด ๋ฉ€ํ‹ฐ ์Šค๋ ˆ๋“œ ํ”„๋กœ๊ทธ๋ž˜๋ฐ์ด ์–ด๋ ต๋‹ค.

  • ๊ฐ’์„ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์—†๋Š” ๊ฒฝ์šฐ ํ”„๋กœ๊ทธ๋žจ์˜ ์ •ํ™•์„ฑ์„ ๋†’์—ฌ ๋ฒ„๊ทธ์˜ ๋ฐœ์ƒ ๊ฐ€๋Šฅ์„ฑ์„ ์ค„์ธ๋‹ค.

์ผ๊ธ‰ ๊ฐ์ฒด๋กœ์„œ์˜ ํ•จ์ˆ˜

  • ํ•จ์ˆ˜ํ˜• ํ”„๋กœ๊ทธ๋ž˜๋ฐ์—์„œ๋Š” ํ•จ์ˆ˜๊ฐ€ ์ผ๊ธ‰ ๊ฐ์ฒด(first-class citizen)์˜ ์—ญํ• ์„ ์ˆ˜ํ–‰

  • ํ•จ์ˆ˜๋ฅผ ์ผ๊ธ‰ ๊ฐ์ฒด๋กœ ํ™œ์šฉ์ด ๊ฐ€๋Šฅํ•  ๊ฒฝ์šฐ ํ•จ์ˆ˜๋ฅผ ํ•จ์ˆ˜์˜ ์ธ์ž๋กœ ๋ฐ›๊ฑฐ๋‚˜ ํ•จ์ˆ˜์˜ ๋ฐ˜ํ™˜ ๊ฐ’์œผ๋กœ ํ™œ์šฉํ•˜๋Š” ๊ฒƒ์ด ๊ฐ€๋Šฅ

๋žŒ๋‹ค

  • ๋žŒ๋‹ค๋Š” ์ต๋ช… ํ•จ์ˆ˜์˜ ๋‹ค๋ฅธ ํ‘œํ˜„

  • ์ฆ‰, ํ•จ์ˆ˜๋Š” ํ•จ์ˆ˜์ธ๋ฐ ์ด๋ฆ„์ด ์—†๋Š” ๊ฒฝ์šฐ๋ฅผ ์˜๋ฏธ

ํ•จ์ˆ˜ํ˜• ํ”„๋กœ๊ทธ๋ž˜๋ฐ์„ ์—ฐ์Šตํ•˜๋Š” ๋ฐฉ๋ฒ•

  • ํ”„๋กœ๊ทธ๋ž˜๋ฐ์˜ ๊ธฐ๋ณธ ํ‹€์€ ๊ฐ์ฒด ์ง€ํ–ฅ ํ”„๋กœ๊ทธ๋ž˜๋ฐ

  • ํ•จ์ˆ˜ ๋‚ด๋ถ€ ๊ตฌํ˜„์€ ํ•จ์ˆ˜ํ˜• ํ”„๋กœ๊ทธ๋ž˜๋ฐ์„ ์ง€ํ–ฅ

  • ๊ฐ์ฒด์˜ ์ƒํƒœ ๊ด€๋ฆฌ๋Š” ๋ถˆ๋ณ€์„ ์ง€ํ–ฅ

์‹œํ€€์Šค

์ฝ”ํ‹€๋ฆฐ ์ปฌ๋ ‰์…˜์˜ ํ•จ์ˆ˜๋Š” ๊ฒฐ๊ณผ ์ปฌ๋ ‰์…˜์„ ์ฆ‰์‹œ ์ƒ์„ฑ

์ปฌ๋ ‰์…˜ ํ•จ์ˆ˜๋ฅผ ์—ฐ์‡„ํ•˜๋ฉด ๋งค ๋‹จ๊ณ„๋งˆ๋‹ค ๊ณ„์‚ฐ ์ค‘๊ฐ„ ๊ฒฐ๊ณผ๋ฅผ ์ƒˆ๋กœ์šด ์ปฌ๋ ‰์…˜์— ์ž„์‹œ๋กœ ๋ณด๊ด€

  • ์•„๋ž˜ ์—ฐ์‡„ ํ˜ธ์ถœ์€ ๋ฆฌ์ŠคํŠธ๋ฅผ 2๊ฐœ ์ƒ์„ฑ(map์˜ ๊ฒฐ๊ณผ์™€ filter์˜ ๊ฒฐ๊ณผ)

people.map(Person::name).filter { it.startWith("A") }

์‹œํ€€์Šค๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์ค‘๊ฐ„ ์ž„์‹œ ์ปฌ๋ ‰์…˜์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ณ ๋„ ์ปฌ๋ ‰์…˜ ์—ฐ์‚ฐ์„ ์—ฐ์‡„

  • ์›์†Œ๊ฐ€ ๋งŽ์€ ๊ฒฝ์šฐ ์„ฑ๋Šฅ์ด ๋ˆˆ์— ๋„๊ฒŒ ํ–ฅ์ƒ

  • ์ž๋ฐ” ์ŠคํŠธ๋ฆผ๊ณผ ๋™์ผํ•œ ๊ฐœ๋…

people.asSequence()
    .map(Person::name)
    .filter { it.startWith("A") }
    .toList()

๐Ÿ’ก ํฐ ์ปฌ๋ ‰์…˜์— ๋Œ€ํ•ด์„œ ์—ฐ์‚ฐ์„ ์—ฐ์‡„์‹œํ‚ฌ ๋•Œ๋Š” ์‹œํ€€์Šค๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์„ ๊ทœ์น™์œผ๋กœ ์‚ผ์ž.

๐Ÿ’ก ์‹œํ€€์Šค ์›์†Œ๋ฅผ ์ธ๋ฑ์Šค๋ฅผ ์‚ฌ์šฉํ•ด ์ ‘๊ทผํ•˜๋Š” ๋“ฑ์˜ ๋‹ค๋ฅธ API ๋ฉ”์„œ๋“œ๊ฐ€ ํ•„์š”ํ•˜๋‹ค๋ฉด ์‹œํ€€์Šค๋ฅผ ๋ฆฌ์ŠคํŠธ๋กœ ๋ฐ˜ํ™˜ํ•˜์ž.

Example

์ต๋ช… ํด๋ž˜์Šค๋ฅผ ๋žŒ๋‹ค๋กœ ์ „ํ™˜ํ•˜๊ธฐ

/**
 * AS-IS
 */
interface MoveStrategy {
    val isMovable: Boolean
}

data class Car(val name: String, val position: Int) {
    fun move(moveStrategy:MoveStrategy): Car {
        if (moveStrategy.isMovable) {
            return copy(position = position + 1)
        }
        return this
    }
}

@Test
fun ์ด๋™() {
    val car = Car("jason", 0)
    val actual: Car = car.move(object : MoveStrategy {
        override val isMovable: Boolean = true
    })
    assertEquals(Car("jason", 1), actual)
}

@Test
fun ์ •์ง€() {
    val car = Car("jason", 0)
    val actual: Car = car.move(object : MoveStrategy {
        override val isMovable: Boolean = false
    })
    assertEquals(Car("jason", 0), actual)
}

/**
 * TO-BE
 */
 // ๋žŒ๋‹ค ์ ์šฉ์„ ์œ„ํ•ด ํ•จ์ˆ˜ํ˜• ์ธํ„ฐํŽ˜์ด์Šค๋กœ ๋ณ€๊ฒฝ
fun interface MoveStrategy {
    fun isMovable(): Boolean
}

data class Car(val name: String, val position: Int) {
    fun move(moveStrategy: MoveStrategy): Car {
        if (moveStrategy.isMovable()) {
            return copy(position = position + 1)
        }
        return this
    }
}

@Test
fun ์ด๋™() {
    val car = Car("jason", 0)
    val actual: Car = car.move { true }
    assertEquals(Car("jason", 1), actual)
}

@Test
fun ์ •์ง€() {
    val car = Car("jason", 0)
    val actual: Car = car.move { false }
    assertEquals(Car("jason", 0), actual)
}

๋žŒ๋‹ค๋ฅผ ํ™œ์šฉํ•ด ์ค‘๋ณต ์ œ๊ฑฐํ•˜๊ธฐ

/**
 * AS-IS
 */
val numbers: List<Int> = listOf(1, 2, 3, 4, 5, 6)

fun sumAll(numbers:List<Int>): Int {
    var total = 0
    for (number in numbers) {
        total += number
    }
    return total
}

fun sumAllEven(numbers:List<Int>): Int {
    var total = 0
    for (number in numbers) {
        if (number % 2 == 0) {
            total += number
        }
    }
    return total
}

fun sumAllOverThree(numbers:List<Int>): Int {
    var total = 0
    for (number in numbers) {
        if (number > 3) {
            total += number
        }
    }
    return total
}

/**
 * TO-BE
 */
 val numbers: List<Int> = listOf(1, 2, 3, 4, 5, 6)

fun sumByCondition(
    numbers: List<Int>, condition: (Int) -> Boolean): Int {
    var total = 0
    for (number in numbers) {
        if (condition(number)) {
            total += number
        }
    }
    return total
}

fun sumAll(numbers: List<Int>): Int {
    return sumByCondition(numbers) { true }
}

fun sumAllEven(numbers: List<Int>): Int {
    return sumByCondition(numbers) { it % 2 == 0 }
}

fun sumAllOverThree(numbers: List<Int>): Int {
    return sumByCondition(numbers) { it > 3 }
}

์ฝ”ํ‹€๋ฆฐ DSL

์ฝ”ํ‹€๋ฆฐ DSL

  • ๋ฒ”์šฉ ์–ธ์–ด(=์ฝ”ํ‹€๋ฆฐ)๋กœ ์ž‘์„ฑ๋œ ํ”„๋กœ๊ทธ๋žจ์˜ ์ผ๋ถ€

  • ๋ฒ”์šฉ ์–ธ์–ด์™€ ๋™์ผํ•œ ๋ฌธ๋ฒ•์„ ์‚ฌ์šฉ

  • ํ˜ธ์ถœ ๊ฒฐ๊ณผ๋ฅผ ๊ฐ์ฒด๋กœ ๋ณ€ํ™˜ํ•˜๊ธฐ ์œ„ํ•ด ๋…ธ๋ ฅํ•  ํ•„์š”๊ฐ€ ์—†์Œ

  • ํƒ€์ž… ์•ˆ์ „์„ฑ์„ ๋ณด์žฅ

  • ์ฝ”ํ‹€๋ฆฐ ์ฝ”๋“œ๋ฅผ ์›ํ•˜๋Š” ๋Œ€๋กœ ์‚ฌ์šฉ ๊ฐ€๋Šฅ

์ฝ”ํ‹€๋ฆฐ์€ ๊ฐ„๊ฒฐํ•œ ๊ตฌ๋ฌธ์„ ์ง€์›

  • ํ™•์žฅ ํ•จ์ˆ˜(Extension functions)

    /**
     * AS-IS
     */
    object StringUtils {
        fun lastChar(s:String): Char {
            return s.get(s.length - 1)
        }
    }
    
    @Test
    fun `before`() {
        assertEquals('n', StringUtils.lastChar("Kotlin"))
    }
    
    ...
    
    /**
     * TO-BE
     */
    @Test
    fun `after`() {
        // fun String.lastChar(): Char = this.get(this.length - 1)
        fun String.lastChar(): Char {
            return this.get(this.length - 1)
        }
        assertEquals('n', "Kotlin".lastChar())
    }
  • ์ค‘์œ„ ํ˜ธ์ถœ(Infix notation)

    @Test
    fun `before`() {
        fun Any.to(other:Any) = Pair(this, other)
        assertEquals(Pair(1, "one"), 1.to("one"))
    }
    
    @Test
    fun `after`() {
        infix fun Any.to(other:Any) = Pair(this, other)
        assertEquals(Pair(1, "one"), 1 to "one")
    
    }
  • ์—ฐ์‚ฐ์ž ์˜ค๋ฒ„๋กœ๋”ฉ(Operator overloading)

    @Test
    fun `before`() {
        data class Point(val x: Int, val y: Int) {
            fun plus(other:Point): Point = Point(x + other.x, y + other.y)
        }
    
        assertEquals(Point(1, 3), Point(0, 1).plus(Point(1, 2)))
    }
    
    @Test
    fun `after`() {
        data class Point(val x: Int, val y: Int) {
            operator fun plus(other:Point): Point = Point(x + other.x, y + other.y)
        }
    
        assertEquals(Point(1, 3), Point(0, 1) + Point(1, 2))
    }
  • get ๋ฉ”์„œ๋“œ์— ๋Œ€ํ•œ ๊ด€๋ก€(Indexed access operator)

    val names = listOf("Aaron", "Park")
    assertEquals("Aaron", names.get(0))
    assertEquals("Aaron", names[0])
  • ๋žŒ๋‹ค๋ฅผ ๊ด„ํ˜ธ ๋ฐ–์œผ๋กœ ๋นผ๋‚ด๋Š” ๊ด€๋ก€(Passing a lambda to the last parameter)

    @Test
    fun `before`() {
        assertThrows<IllegalStateException> {
            check(false, { -> "Check failed." })
        }
    }
    
    @Test
    fun `after`() {
        assertThrows<IllegalStateException> {
            check(false) { "Check failed." }
        }
    }
  • ์ˆ˜์‹  ๊ฐ์ฒด ์ง€์ • ๋žŒ๋‹ค(Lambda with receiver)

    @Test
    fun `before`() {
        val sb = StringBuilder()
        sb.append("Yes")
        sb.append("No")
        assertEquals("YesNo", sb.toString())
    }
    
    @Test
    fun `after`() {
        val sb = StringBuilder()
        sb.apply {
            this.append("Yes")
            append("No")
        }
        assertEquals("YesNo", sb.toString())
    }

์ฝ”ํ‹€๋ฆฐ DSL ์‹ค์Šต

import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.ValueSource
import kotlin.test.Test
import kotlin.test.assertEquals

class DslTest {

    /**
     * block : PersonBuilder ๋‚ด๋ถ€์˜ ํ•จ์ˆ˜๋“ค์„ ํ˜ธ์ถœํ•˜์—ฌ Person ๊ฐ์ฒด์˜ ์†์„ฑ์„ ์„ค์ •ํ•˜๋Š” ๋ฐ ์‚ฌ์šฉ
     * apply : ์ˆ˜์‹  ๊ฐ์ฒด(PersonBuilder)์— ๋Œ€ํ•ด block ์—์„œ ์ •์˜๋œ ์„ค์ •์„ ์ ์šฉ
     *   - ์ฆ‰, block ์€ PersonBuilder ์˜ ์ปจํ…์ŠคํŠธ์—์„œ ์‹คํ–‰
     */
    fun introduce(block:PersonBuilder.() ->Unit): Person {
        return PersonBuilder().apply(block).build()
    }

    class PersonBuilder {
        private lateinit var name: String
        private var company: String? = null
        private var skills = mutableListOf<Skill>()
        private var languages = mutableMapOf<String, Int>()

        fun name(value:String) {
            name = value
        }

        fun company(value:String) {
            company = value
        }

        fun skills(block: Skills.() -> Unit) {
            skills.addAll(Skills().apply(block).skills)
        }

        fun languages(block: Languages.() -> Unit) {
            languages.putAll(Languages().apply(block).languages)
        }

        fun build(): Person {
            return Person(name, company, skills, languages)
        }
    }

    data class Person(val name: String, var company: String?, var skills: List<Skill>, var languages: Map<String, Int>)

    class Skills {
        val skills = mutableListOf<Skill>()

        fun soft(description: String) {
            skills.add(Skill("Soft", description))
        }

        fun hard(description: String) {
            skills.add(Skill("Hard", description))
        }
    }

    class Skill(val type: String, val description: String)

    class Languages {
        val languages = mutableMapOf<String, Int>()

        infix fun String.level(level: Int) {
            languages[this] = level
        }
    }

    @ValueSource(strings = ["aaron", "kko"])
    @ParameterizedTest
    fun introduce(value:String) {
        val person = introduce {
            name(value)
        }
        assertEquals(person.name, value)
        assertEquals(person.company, null)
    }

    @Test
    fun company() {
        val person = introduce {
            name("aaron")
            company("kko")
        }
        assertEquals(person.name, "aaron")
        assertEquals(person.company, "kko")
    }

    @Test
    fun dslTest() {
        val introduction = introduce {
            name("aaron")
            company("kko")
            skills {
                soft("A passion for problem solving")
                soft("Good communication skills")
                hard("Kotlin")
            }
            languages {
                "Korean" level 5
                "English" level 3
            }
        }

        assertEquals("aaron", introduction.name)
        assertEquals("kko", introduction.company)

        assertEquals(3, introduction.skills.size)
        assertEquals("Soft", introduction.skills[0].type)
        assertEquals("A passion for problem solving", introduction.skills[0].description)
        assertEquals("Hard", introduction.skills[2].type)
        assertEquals("Kotlin", introduction.skills[2].description)

        assertEquals(2, introduction.languages.size)
        assertEquals(5, introduction.languages["Korean"])
        assertEquals(3, introduction.languages["English"])
    }
}

Last updated