TDD, Clean Code Preview
TDD, Clean Code with Kotlin Preview
TDD, Clean Code with Kotlin ๊ธ์ ์ฐธ๊ณ ํ์ฌ ์์ฑํ ๊ธ์ ๋๋ค.
Start Kotlin
Kotlin Web Compiler Site
๋ณ์์ ์๋ฃํ
โ ๋ณ์์ ์ ์ธ
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
internal
๊ฐ์ ๋ชจ๋ ๋ด์์๋ง ์ ๊ทผ ๊ฐ๋ฅ
private
๊ฐ์ ํ์ผ ๋ด์์๋ง ์ ๊ทผ ๊ฐ๋ฅ
protected
๋ฏธ์ฌ์ฉ
Class Scope
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
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
๐
[Red] : ์คํจํ๋ ์์ ํ ์คํธ ์์ฑ(์ปดํ์ผ ์๋ฌ ๋ฌด์)
[Green] : ํ ์คํธ๊ฐ ํต๊ณผ๋๋๋ก ์์ . ์ด๋ ํ ํด๋ฆฐ์ฝ๋๋ ๊ณ ๋ คํ์ง ์์
[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