TDD, Clean Code with Kotlin Preview
TDD, Clean Code with Kotlin ๊ธ์ ์ฐธ๊ณ ํ์ฌ ์์ฑํ ๊ธ์
๋๋ค.
Start Kotlin
Kotlin Web Compiler Site
https://play.kotlinlang.org/
๋ณ์์ ์๋ฃํ
โ
๋ณ์์ ์ ์ธ
var
: ์ผ๋ฐ์ ์ผ๋ก ํต์ฉ๋๋ ๋ณ์. ์ธ์ ๋ ์ง ์ฝ๊ธฐ ์ฐ๊ธฐ๊ฐ ๊ฐ๋ฅ val
: ์ ์ธ์์๋ง ์ด๊ธฐํ ๊ฐ๋ฅ. ์ค๊ฐ์ ๊ฐ ๋ณ๊ฒฝ ๋ถ๊ฐ
Copy 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์ ํ์ฉํ์ง ์๋๋ค.
๋ณ์์ ๊ฐ์ ํ ๋นํ์ง ์์์ฑ๋ก ์ฌ์ฉํ๊ฒ ๋๋ฉด ์ปดํ์ผ ์๋ฌ
โ
์ฝํ๋ฆฐ์ ๊ธฐ๋ณธ ์๋ฃํ
์๋ฐ์์ ํธํ์ ์ํด ์๋ฐ์ ๊ฑฐ์ ๋์ผ
Copy 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"""
}
์ง์๋๋ ํน์๋ฌธ์
ํ๋ณํ๊ณผ ๋ฐฐ์ด
โ
ํ๋ณํ
์ฝํ๋ฆฐ์ ํ๋ณํ ์ ๋ฐ์ํ ์ ์๋ ์ค๋ฅ๋ฅผ ๋ง๊ธฐ ์ํด ์์์ ํ๋ณํ์ ๋ฏธ์ง์
Copy fun main() {
// ๋ช
์์ ํ๋ณํ
var a: Int = 54321
var b: Long = a.toLong()
}
โ
๋ฐฐ์ด
arrayOf
, arrayOfNulls
Copy fun main() {
// ๊ฐ์ด ์๋ ๋ฐฐ์ด ์์ฑ
var intArr = arrayOf(1, 2, 3, 4, 5)
// ํน์ ํฌ๊ธฐ๋ฅผ ๊ฐ์ง ๋น์ด์๋ ๋ฐฐ์ด ์์ฑ
var nullArr = arrayOfNulls<Int>(5)
intArr[2] = 8
println(intArr[4])
}
ํ์
์ถ๋ก ๊ณผ ํจ์
โ
ํ์
์ถ๋ก
๋ณ์ ํจ์๋ค์ ์ ์ธํ ๋๋ ์ฐ์ฐ์ด ์ด๋ฃจ์ด์ง ๋ ์๋ฃํ์ ์ฝ๋์ ๋ช
์ํ์ง ์์๋ ์๋์ผ๋ก ์๋ฃํ์ ์ถ๋ก
๋ฐ๋ ํน์ ํ ์๋ฃํ์ผ๋ก ์ง์ ํด์ผํ๋ ์ํฉ์ด ์๋๋ผ๋ฉด ๋๋ถ๋ถ์ ์ฝํ๋ฆฐ์ ํ์
์ถ๋ก ๊ธฐ๋ฅ์ ์ด์ฉ
โ
ํจ์
์ฝํ๋ฆฐ์์ ํจ์๋ ๋ด๋ถ์ ์ผ๋ก ๊ธฐ๋ฅ์ ๊ฐ์ง ํํ์ง๋ง, ์ธ๋ถ์์ ๋ณผ ๋๋ ํ๋ผ๋ฏธํฐ๋ฅผ ๋ฃ๋๋ค๋ ์ ์ธ์๋ ์๋ฃํ์ด ๊ฒฐ์ ๋ ๋ณ์๋ผ๋ ๊ฐ๋
์ผ๋ก ์ ๊ทผ
Copy 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
Copy 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
๋ฑํธ๋ ๋ถ๋ฑํธ๋ ์ฌ์ฉ ๋ถ๊ฐ
Copy 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
ํธํ ์ฌ๋ถ๋ฅผ ์ฒดํฌํ๊ณ ํ๋ณํ๊น์ง ํ๋ฒ์ ์งํ
๋ฐ๋ณต๋ฌธ
๋ค๋ฅธ ์ธ์ด์์์ ๋ฐ๋ณต๋ฌธ๊ณผ๋ ์ฝ๊ฐ์ ์ฐจ์ด๊ฐ ์๋ค.
Copy 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
}
๋ ์ด๋ธ์ด ๋ฌ๋ฆฐ ๋ฐ๋ณต๋ฌธ ๊ธฐ์ค์ผ๋ก ๋ฐ๋ณต๋ฌธ์ ์ข
๋ฃ์์ผ์ฃผ๋ ๊ธฐ๋ฅ
๋ ์ด๋ธ ์ด๋ฆ๊ณผ @๊ธฐํธ๋ก ์ฆ์ ๋ฐ๋ณต๋ฌธ ์ข
๋ฃ
Copy loop@for (i in 1..10) {
for (j in 1..10) {
if (i == 1 && j == 2) break@loop
println("i : $i, j : $j")
}
}
ํด๋์ค
Copy 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}์
๋๋ค.")
}
}
Copy [init] 1990๋
์ ๋ฐ๋ณด์๋์ ์ธ์คํด์ค๊ฐ ์์ฑ๋์์ต๋๋ค.
[init] 1997๋
์ ์ ์ ๊ตญ๋์ ์ธ์คํด์ค๊ฐ ์์ฑ๋์์ต๋๋ค.
[init] 2004๋
์ ์ฅ์์๋์ ์ธ์คํด์ค๊ฐ ์์ฑ๋์์ต๋๋ค.
์๋
ํ์ธ์. 1990๋
์ ๋ฐ๋ณด์์
๋๋ค.
[introduce] ์๋
ํ์ธ์. 1997๋
์ ์ ์ ๊ตญ์
๋๋ค.
[introduce] ์๋
ํ์ธ์. 2004๋
์ ์ฅ์์์
๋๋ค.
[init] 1997๋
์ ์ด๋ฃจ๋ค๋์ ์ธ์คํด์ค๊ฐ ์์ฑ๋์์ต๋๋ค.
[constructor] ๋ณด์กฐ ์์ฑ์๊ฐ ์ฌ์ฉ๋์์ต๋๋ค.
[init] 1997๋
์ ์ฐจ์์ฐ๋์ ์ธ์คํด์ค๊ฐ ์์ฑ๋์์ต๋๋ค.
[constructor] ๋ณด์กฐ ์์ฑ์๊ฐ ์ฌ์ฉ๋์์ต๋๋ค.
[init] 1997๋
์ ๋ฅ์์ ๋์ ์ธ์คํด์ค๊ฐ ์์ฑ๋์์ต๋๋ค.
[constructor] ๋ณด์กฐ ์์ฑ์๊ฐ ์ฌ์ฉ๋์์ต๋๋ค.
์์
Copy 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("์ผ์น์ผ์น")
}
}
์ค๋ฒ๋ผ์ด๋ฉ
Copy fun main() {
var t = Tiger()
t.eat()
}
/**
* ์์์ด ๊ฐ๋ฅํ๋๋ก open ๋ ํด๋์ค
*/
open class Animal () {
// ์ํผ ํด๋์ค์์ open ๋ ํจ์๋ ์๋ธ ํด๋์ค์์ override ๊ฐ๋ฅ
open fun eat() {
println("์์์ ๋จน์ต๋๋ค")
}
}
class Tiger : Animal() {
override fun eat() {
println("๊ณ ๊ธฐ๋ฅผ ๋จน์ต๋๋ค")
}
}
์ถ์ํ
์ถ์ ํด๋์ค: ์ถ์ ํจ์๋ฅผ ํฌํจํ๋ ํด๋์ค
Copy 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 ํจ์๋ก ๊ฐ์ฃผ
โ ๏ธ ์ฌ๋ฌ๊ฐ์ ์ธํฐํ์ด์ค๋ ํด๋์ค์์ ๊ฐ์ ์ด๋ฆ๊ณผ ํํ๋ฅผ ๊ฐ์ง ํจ์๋ฅผ ๊ตฌํํ๊ณ ์๋ค๋ฉด,
์๋ธํด๋์ค์์๋ ํผ์ ์ด ์ผ์ด๋์ง ์๋๋ก ๋ฐ๋์ ์ค๋ฒ๋ผ์ด๋ฉํ์ฌ ์ฌ๊ตฌํ ํ์
Copy 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
์์ค ์ฝ๋์ ์์์ ์ง์ ํ๊ธฐ ์ํ ๋
ผ๋ฆฌ์ ๋จ์
์ผ๋ฐ์ ์ผ๋ก ํจํค์ง ์ด๋ฆ์ ์ง์ ๋ ํ์ฌ ๋๋ฉ์ธ์ ๊ฑฐ๊พธ๋ก ํด์ ํ๋ก์ ํธ๋ช
๊ณผ ์ธ๋ถ ๊ธฐ๋ฅ์ ๋ถ์ด๋ ๋ฐฉ์
์ฝํ๋ฆฐ์ ์๋ฐ์ ๋ฌ๋ฆฌ ํด๋ ๊ตฌ์กฐ์ ํจํค์ง ๋ช
์ ์ผ์น์ํค์ง ์์๋ ๋๋ค.
๋จ์ํ ํ์ผ ์๋จ์ ํจํค์ง๋ง ๋ช
์ํด ์ฃผ๋ฉด ์ปดํ์ผ๋ฌ๊ฐ ์์์ ์ฒ๋ฆฌ
์ฝํ๋ฆฐ์ ํด๋์ค๋ช
๊ณผ ํ์ผ๋ช
์ด ์ผ์นํ์ง ์์๋ ๋๋ฉฐ,
ํ๋์ ํ์ผ์ ์ฌ๋ฌ๊ฐ์ ํด๋์ค๋ฅผ ๋ฃ์ด๋ ์์์ ์ปดํ์ผ ๊ฐ๋ฅ
ํ์ผ์ด๋ ํด๋ ๊ธฐ์ค์ผ๋ก ๊ตฌ๋ถํ์ง ์๊ณ ํ์ผ๋ด์ ์๋ package
ํค์๋ ๊ธฐ์ค์ผ๋ก ๊ตฌ๋ถ
์ค์ฝํ์ ์ ๊ทผ ์ ํ์
โ
์ค์ฝํ
ํจํค์ง ์์ ๋ณ์, ํจ์, ํด๋์ค๋ ๋ชจ๋ ํ๋์ ์ค์ฝํ์ ์๋ ๋ฉค๋ฒ
ํจ์, ํด๋์ค์์ ๋๋ค๋ฅธ ๋ณ์, ํจ์๊ฐ ์กด์ฌํ๋ค๋ฉด ํจํค์ง ์์ ๋๋ค๋ฅธ ํ์ ์ค์ฝํ๋ก ๋์
์ค์ฝํ์ ๋ํ ์ธ ๊ฐ์ง ๊ท์น
(1) ์ค์ฝํ ์ธ๋ถ์์๋ ์ค์ฝํ ๋ด๋ถ์ ๋ฉค๋ฒ๋ฅผ ์ฐธ์กฐ์ฐ์ฐ์
๋ก๋ง ์ฐธ์กฐ ๊ฐ๋ฅ
Copy a.eat()
import com.google.aaron
import com.google.aaron.A
(2) ๋์ผ ์ค์ฝํ ๋ด์์๋ ๋ฉค๋ฒ๋ค์ ๊ณต์
ํ ์ ์์
(3) ํ์ ์ค์ฝํ์์๋ ์์ ์ค์ฝํ์ ๋ฉค๋ฒ๋ฅผ ์ฌ์ ์ ๊ฐ๋ฅ
โ
์ ๊ทผ ์ ํ์
๋ณ์, ํจ์, ํด๋์ค ์ ์ธ ์ ๋งจ ์์ ๋ถ์ฌ ์ฌ์ฉ
์ค์ฝํ ์ธ๋ถ์์ ์ค์ฝํ ๋ด๋ถ์ ์ ๊ทผํ ๋ ๊ทธ ๊ถํ์ ๊ฐ๋ฐ์๊ฐ ์ ์ด
ํ ์ ์๋ ๊ธฐ๋ฅ
Package Scope
public (default)
์ด๋ค ํจํค์ง์์๋ ์ ๊ทผ ๊ฐ๋ฅ
๊ฐ์ ๋ชจ๋ ๋ด์์๋ง ์ ๊ทผ ๊ฐ๋ฅ
๊ฐ์ ํ์ผ ๋ด์์๋ง ์ ๊ทผ ๊ฐ๋ฅ
Class Scope
public (default)
ํด๋์ค ์ธ๋ถ์์ ํญ์ ์ ๊ทผ ๊ฐ๋ฅ
ํด๋์ค ๋ด๋ถ์์๋ง ์ ๊ทผ ๊ฐ๋ฅ
ํด๋์ค ์์ ๊ณผ ์์๋ฐ์ ํด๋์ค์์ ์ ๊ทผ ๊ฐ๋ฅ
๊ณ ์ฐจํจ์์ ๋๋คํจ์
โ
๊ณ ์ฐจํจ์
ํจ์๋ฅผ ๋ง์น ํด๋์ค์์ ๋ง๋ค์ด๋ธ ์ธ์คํด์ค์ฒ๋ผ
์ทจ๊ธํ๋ ๋ฐฉ๋ฒ
ํจ์๋ฅผ ํ๋ผ๋ฏธํฐ
๋ก ๋๊ฒจ์ค ์๋ ์๊ณ , ๊ฒฐ๊ณผ๊ฐ
์ผ๋ก ๋ฐํ๋ฐ์ ์๋ ์๋ ๋ฐฉ๋ฒ
์ฝํ๋ฆฐ์์๋ ๋ชจ๋ ํจ์๋ฅผ ๊ณ ์ฐจํจ์๋ก ์ฌ์ฉ ๊ฐ๋ฅ
::
โ ์ผ๋ฐ ํจ์๋ฅผ ๊ณ ์ฐจ ํจ์๋ก ๋ณ๊ฒฝํด ์ฃผ๋ ์ฐ์ฐ์
ํจ์๋ฅผ ํ๋ผ๋ฏธํฐ๋ก ๋ฐ์ ๊ฒฝ์ฐ ํ์
์ ํจ์์ (ํ๋ผ๋ฏธํฐ ์๋ฃํ) -> ๋ฐํํ ์๋ฃํ
Copy fun main() {
b(::a) // ์ผ๋ฐ ํจ์๋ฅผ ๊ณ ์ฐจ ํจ์๋ก ๋ณ๊ฒฝ
}
fun a (str: String) {
println("$str ํจ์ a")
}
// ํจ์๋ฅผ ํ๋ผ๋ฏธํฐ๋ก ๋ฐ๊ธฐ. (ํ๋ผ๋ฏธํฐ ์๋ฃํ) -> ๋ฐํํ ์๋ฃํ
fun b (function: (String)->Unit) {
function("b๊ฐ ํธ์ถํ")
}
โ
๋๋คํจ์
๋๋คํจ์๋ ์ผ๋ฐํจ์์ ๋ฌ๋ฆฌ ๊ทธ ์์ฒด๊ฐ ๊ณ ์ฐจํจ์์ด๋ฏ๋ก ๋ณ๋์ ์ฐ์ฐ์ ์์ด ๋ณ์์ ๋ด์ ์ ์๋ค.
Copy 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 ์์์ ์ง์ ์ธ์คํด์ค์ ์์ฑ๊ณผ ํจ์๋ฅผ ์ฐธ์กฐ์ฐ์ฐ์ ์์ด ์ฌ์ฉ ๊ฐ๋ฅ
๋ํ ์ธ์คํด์ค ์์ ์ ๋ค์ ๋ฐํํ๋ฏ๋ก ์์ฑ๋์๋ง์ ์กฐ์๋ ์ธ์คํด์ค๋ฅผ ๋ณ์์ ๋ฐ๋ก ์ด๊ธฐํ ๊ฐ๋ฅ
Copy 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์ ๋์ผํ๊ฒ ์ค์ฝํ ์์์ ์ฐธ์กฐ์ฐ์ฐ์๋ฅผ ์ฌ์ฉํ์ง ์์๋ ๋๋ค๋ ์ ์ ๊ฐ์ง๋ง, ์ผ๋ฐ ๋๋คํจ์์ฒ๋ผ ์ธ์คํด์ค๋์ ๊ฒฐ๊ณผ๊ฐ์ ๋ฐํ
๐ฆ with
run๊ณผ ๋์ผํ ๊ธฐ๋ฅ์ ๊ฐ์ง์ง๋ง, ์ธ์คํด์ค๋ฅผ ์ฐธ์กฐ์ฐ์ฐ์ ๋์ ํ๋ผ๋ฏธํฐ๋ก ๋ฐ๋๋ค๋ ์ฐจ์ด
Copy a.run { ... }
with(a) { ... }
...
fun main() {
var a = Book("์ฝํ๋ฆฐ ๊ฐ์", 10000).apply {
name = "[์ดํน๊ฐ] " + name
discount()
}
with(a) {
println("์ํ๋ช
: ${name}, ๊ฐ๊ฒฉ: ${price}์") // ์ํ๋ช
: [์ดํน๊ฐ] ์ฝํ๋ฆฐ ๊ฐ์, ๊ฐ๊ฒฉ: 8000์
}
}
๐ฆ also
apply์ ์ ์ฌํ๊ฒ ์ฒ๋ฆฌ๊ฐ ๋๋๋ฉด ์ธ์คํด์ค๋ฅผ ๋ฐํํ์ง๋ง, ํ๋ผ๋ฏธํฐ๋ก ์ธ์คํด์ค๋ฅผ ๋๊ธด ๊ฒ๊ณผ ๊ฐ์ด it
์ ํตํด ์ธ์คํด์ค๋ฅผ ์ฌ์ฉ
๊ฐ์ ์ด๋ฆ์ ๋ณ์๋ ํจ์๊ฐ ์ค์ฝํ ๋ฐ๊นฅ์ ์ค๋ณต๋์ด ์๋ ๊ฒฝ์ฐ ํผ๋์ ๋ฐฉ์งํ๊ธฐ ์ํจ
Copy fun main() {
var a = Book("์ฝํ๋ฆฐ ๊ฐ์", 10000).also {
it.name = "[์ดํน๊ฐ] " + it.name
it.discount()
}
a.run {
println("์ํ๋ช
: ${name}, ๊ฐ๊ฒฉ: ${price}์") // ์ํ๋ช
: [์ดํน๊ฐ] ์ฝํ๋ฆฐ ๊ฐ์, ๊ฐ๊ฒฉ: 8000์
}
}
๐ฆ let
run๊ณผ ์ ์ฌํ๊ฒ ์ฒ๋ฆฌ๊ฐ ๋๋๋ฉด ์ต์ข
๊ฐ์ ๋ฐํํ์ง๋ง, ํ๋ผ๋ฏธํฐ๋ก ์ธ์คํด์ค๋ฅผ ๋๊ธด ๊ฒ๊ณผ ๊ฐ์ด it
์ ํตํด ์ธ์คํด์ค๋ฅผ ์ฌ์ฉ
๊ฐ์ ์ด๋ฆ์ ๋ณ์๋ ํจ์๊ฐ ์ค์ฝํ ๋ฐ๊นฅ์ ์ค๋ณต๋์ด ์๋ ๊ฒฝ์ฐ ํผ๋์ ๋ฐฉ์งํ๊ธฐ ์ํจ
Copy 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
๋ก ์ ์ธ๋ ๊ฐ์ฒด๋ ์ต์ด ์ฌ์ฉ ์ ์๋์ผ๋ก ์์ฑ๋๊ณ , ์ดํ์๋ ์ฝ๋ ์ ์ฒด์์ ๊ณต์ฉ์ผ๋ก ์ฌ์ฉ๋ ์ ์๋ค.
Copy 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 ๋ฉค๋ฒ์ ์ ์ฌ)
Copy 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
Copy 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์ ํํ๋ ๋น์ทํ์ง๋ง ์ด๋ฆ์ด ์๋ค๋ ์ฐจ์ด
Copy EventPrinter().start()
...
class EventPrinter {
fun start() {
val counter = Counter(object: EventListener {
override fun onEvent(count: Int) {
print("${count}-")
}
})
counter.count()
}
}
ํด๋์ค์ ๋คํ์ฑ
Up-casting
: ์์ ์๋ฃํ์ธ ์ํผํด๋์ค๋ฅผ ๋ณํ
Copy var a: Drink = Cola()
Down-casting
: Up-casting๋ ์ธ์คํด์ค๋ฅผ ๋ค์ ํ์ ์๋ฃํ์ผ๋ก ๋ณํ
as
: ๋ณ์๋ฅผ ํธํ๋๋ ์๋ฃํ์ผ๋ก ๋ณํํด์ฃผ๋ ์บ์คํ
์ฐ์ฐ์
๋ฐํ๊ฐ๋ฟ๋ง ์๋๋ผ ๋ณ์ ์์ฒด๋ ๋ค์ด์บ์คํ
Copy var a: Drink = Cola()
a as Cola // ์ดํ a๋ Cola๋ก ๋์
var b = a as Cola // ๋ณํ ๊ฒฐ๊ณผ๋ฅผ ๋ฐํ๋ฐ์ ๋ณ์์ ์ด๊ธฐํ
is
: ๋ณ์๊ฐ ์๋ฃํ์ ํธํ๋๋์ง ์ฒดํฌํ ํ ๋ณํํด์ฃผ๋ ์บ์คํ
์ฐ์ฐ์ (์กฐ๊ฑด๋ฌธ ๋ด์์ ์ฌ์ฉ)
Copy var a: Drink = Cola()
if (a is Cola) {
// ํด๋น ์์ญ ์์์๋ง a๊ฐ Cola๋ก ์ฌ์ฉ
}
Example
Copy 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}๋ก ์ค๊ฑฐ์ง๋ฅผ ํฉ๋๋ค.")
}
}
์ ๋๋ฆญ
ํด๋์ค๋ ํจ์์์ ์ฌ์ฉํ๋ ์๋ฃํ์ ์ธ๋ถ์์ ์ง์ ํ ์ ์๋ ๊ธฐ๋ฅ
ํจ์๋ ํด๋์ค๋ฅผ ์ ์ธํ ๋ ๊ณ ์ ์ ์ธ ์๋ฃํ ๋์ ์ค์ ์๋ฃํ์ผ๋ก ๋์ฒด๋๋ ํ์
ํ๋ผ๋ฏธํฐ๋ฅผ ๋ฐ์ ์ฌ์ฉ
์ ๋ค๋ฆญ์ ์ฌ์ฉํ ๊ฒฝ์ฐ ์๋ฃํ์ ๋์ฒดํ๊ฒ ๋์ด ์บ์คํ
์ ๋ฐฉ์งํ ์ ์๊ณ , ์ฑ๋ฅ์ ๋์ผ ์ ์๋ค.
ํด๋์ค์ ์ ์ฉ
Copy 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()
}
}
ํจ์์ ์ ์ฉ
Copy 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) ๊ธฐ๋ฅ๋ ์ ๊ณต
Copy 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]
}
๋ฌธ์์ด ๋ค๋ฃจ๊ธฐ
๋ฌธ์์ด ๋ณํ
Copy 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
๋ฌธ์์ด ํ์ธ
Copy 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
๋ฌธ์์ด ํ์
Copy 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 ์ฌ๋ถ์ ๋ฐ๋ผ ๋ค์ ์ค๋ ๊ตฌ๋ฌธ ์คํ ์ฌ๋ถ ํ๋จ
?:
: elvis operator
๊ฐ์ฒด๊ฐ null์ด ์๋๋ผ๋ฉด ๊ทธ๋๋ก ์ฌ์ฉํ์ง๋ง, null์ด๋ผ๋ฉด ์ฐ์ฐ์ ์ฐ์ธก ๊ฐ์ฒด๋ก ๋์ฒด
!!.
: non-null assertion operator
์ฐธ์กฐ์ฐ์ฐ์ ์ฌ์ฉ ์ null ์ฌ๋ถ๋ฅผ ์ปดํ์ผ ์ ํ์ธํ์ง ์๋๋ก ํ์ฌ ๋ฐํ์ ์ NPE์ด ๋ฐ์ํ๋๋ก ์๋์ ์ผ๋ก ๋ฐฉ์น
Copy var a: String? = null
println(a?.toUpperCase()) // null
println(a?:"default".toUpperCase()) // DEFAULT
println(a!!.toUpperCase()) // NPE!!!
null safe ์ฐ์ฐ์๋ ์ค์ฝํ ํจ์์ ์ฌ์ฉํ๋ฉด ํธ๋ฆฌ
null ์ฒดํฌ๋ฅผ ์ํด if ๋ณด๋ค ํธ๋ฆฌํ ๊ธฐ๋ฅ
Copy 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 ๊ฐ์ผ๋ก ํ๋จ
๊ฐ์ฒด์ ๋์ผ์ฑ
Copy 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
ํ๋ผ๋ฏธํฐ๋ฅผ ๋ฐ์์ผ ํ๋ ํจ์์ด์ง๋ง ํ๋ผ๋ฏธํฐ๊ฐ ์๋๋ผ๋ ๊ธฐ๋ณธ๊ฐ์ผ๋ก ๋์ํด์ผ ํ ๊ฒฝ์ฐ ์ฌ์ฉ
Copy 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
์ฌ์ฉ
ํ๋ผ๋ฏธํฐ์ ์์์ ๊ด๊ณ์์ด ํ๋ผ๋ฏธํฐ ์ด๋ฆ์ ์ฌ์ฉํ์ฌ ์ง์ ํ๋ผ๋ฏธํฐ ๊ฐ์ ํ ๋น
Copy deliveryItem("๋
ธํธ๋ถ", destination = "ํ๊ต")
โ
variable number of arguments (vararg
)
๊ฐ์ ์๋ฃํ์ ๊ฐ์์ ์๊ด์์ด ํ๋ผ๋ฏธํฐ๋ก ๋ฐ๊ณ ์ถ์ ๊ฒฝ์ฐ ์ฌ์ฉ
Copy 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 ํจ์๊ฐ ์ ์ฉ๋ ์๋ฃํ.์ด๋ฆ
์ผ๋ก ์ง์
Copy 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
(๋ด๋ถ ํด๋์ค)
์ธ๋ถ ํด๋์ค ๊ฐ์ฒด ์์์ ์ฌ์ฉ๋๋ ํด๋์ค๋ก ์ธ๋ถ ํด๋์ค์ ์์ฑ๊ณผ ํจ์ ์ฌ์ฉ ๊ฐ๋ฅ
ํผ์์ ๊ฐ์ฒด๋ฅผ ๋ง๋ค ์๋ ์๊ณ , ์ธ๋ถ ํด๋์ค์ ๊ฐ์ฒด๊ฐ ์์ด์ผ๋ง ์์ฑ๊ณผ ์ฌ์ฉ์ด ๊ฐ๋ฅ
Copy 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()
Copy // ํ๋ผ๋ฏธํฐ๊ฐ ์๋ ๊ฒฝ์ฐ ๋๊ฐ์ ๋ด์ฉ์ผ๋ก ์์ฑ
val a = Data("A", 7)
val b = a.copy()
// ํ๋ผ๋ฏธํฐ๊ฐ ์์ผ๋ฉด ํด๋น ํ๋ผ๋ฏธํฐ๋ก ๊ต์ฒดํ์ฌ ์์ฑ
val a = Data("A", 7)
val b = a.copy("B")
Example
Copy 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(): ์์ฑ์ ์์๋๋ก ๋ฐํ
Copy Data("A", 7)
component1() -> "A"
component2() -> 7
listOf(Data("A", 7), Data("B", 1))
component1() -> Data("A", 7)
component2() -> Data("B", 1)
Example
Copy 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์ ๊ฐ์ฒด๋ค์ ๊ณ ์ ํ ์์ฑ์ ๊ฐ์ง ์ ์์
Copy 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
์์๊ฐ ์ ๋ ฌ๋์ง ์์ผ๋ฉฐ, ์ค๋ณต์ด ํ์ฉ๋์ง ์๋ ์ปฌ๋ ์
Copy 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๋ฅผ ์์ผ๋ก ๋ฃ์ด์ฃผ๋ ์ปฌ๋ ์
Copy 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 } : ์กฐ๊ฑด์ ๋ง๋ ์์ดํ
๊ฐ์ ๋ฐํ
Copy 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 }
Copy 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๋ก ๋งค์นญํ์ฌ ์ ์ปฌ๋ ์
์ผ๋ก ์์ฑ
๊ฒฐ๊ณผ ๋ฆฌ์คํธ์ ์์ดํ
๊ฐ์๋ ๋ ์์ ์ปฌ๋ ์
์ ๋ฐ๋ผ๊ฐ
Copy 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)]
}
๋ณ์์ ๊ณ ๊ธ ๊ธฐ์
โ
์์
์ปดํ์ผ ์์ ์ ๊ฒฐ์ ๋์ด ๋ฐ๊ฟ ์ ์๋ ๊ฐ
Copy const val CONST_A = 1234
์์๋ก ์ ์ธ๋ ์ ์๋ ๊ฐ์ ๊ธฐ๋ณธ ์๋ฃํ๋ง ๊ฐ๋ฅ
๋ฐํ์์ ์์ฑ๋ ์ ์๋ ์ผ๋ฐ์ ์ธ ๋ค๋ฅธ ํด๋์ค์ ๊ฐ์ฒด๋ค์ ๋ด์ ์ ์๋ค.
ํด๋์ค์ ์์ฑ์ด๋ ์ง์ญ๋ณ์๋ก๋ ์ฌ์ฉ ๋ถ๊ฐ
๋ฐ๋์ companion object
์์ ์ ์ธํ์ฌ ๊ฐ์ฒด์ ์์ฑ๊ณผ ๊ด๊ณ์์ด ํด๋์ค์ ๊ด๊ณ๋ ๊ณ ์ ์ ์ธ ๊ฐ์ผ๋ก๋ง ์ฌ์ฉ ๊ฐ๋ฅ
๋ณ์์ ๊ฒฝ์ฐ ๋ฐํ์ ์ ๊ฐ์ฒด ์์ฑ์ ์๊ฐ์ด ๋ ์์๋์ด ์ฑ๋ฅ ํ๋ฝ์ด ์์ด ์ด๋ฅผ ๋ง๊ณ ์ ์์๋ฅผ ์ฌ์ฉ
Example
Copy 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 ๋ณ์์ ์ด๊ธฐํ ์ฌ๋ถ ํ์ธ
Example
Copy 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 ๋ณ์ ์ฌ์ฉ ์์ ์ ์ด๊ธฐํ
Copy val a: Int by lazy { 7 }
...
println(a) // ์ด ์์ ์ 7๋ก ์ด๊ธฐํ
Example
Copy 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์์ ์ ์ด๋๋๋ก ์์ฑ๋ ์ ์์
Copy // ์์ฑ๋ ์ค์ฝํ์์
val scope = CoroutineScope(Dispatcher.Defaunt)
// ์๋ก์ด ์ฝ๋ฃจํด ์์ฑ
val coroutineA = scope.launch {}
val coroutineB = scope.async {}
launch vs. async : ๋ฐํ๊ฐ์ด ์๋์ง์ ์ฌ๋ถ
launch
: ๋ฐํ๊ฐ์ด ์๋ Job ๊ฐ์ฒด
Copy 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 ๊ฐ์ฒด
Copy async {
var sum = 0
for (i in 1..10) {
sum++
}
sum // ์ด ๊ฐ์ด ๋ฐํ
}
โ
๋ฃจํด์ ๋๊ธฐ๋ฅผ ์ํ ์ถ๊ฐ์ ์ธ ํจ์๋ค
์ฝ๋ฃจํด ๋ด๋ถ ๋๋ runBlocking ๊ฐ์ ๋ฃจํด์ ๋๊ธฐ๊ฐ ๊ฐ์ผํ ๊ตฌ๋ฌธ ์์์๋ง ๋์ ๊ฐ๋ฅ
delay
(milisecond: Long): ms ๋จ์๋ก ๋ฃจํด์ ์ ์ ๋๊ธฐ์ํค๋ ํจ์
Job.join
(): Job์ ์คํ์ด ๋๋ ๋๊น์ง ๋๊ธฐํ๋ ํจ์
Deferred.await
(): Deferred์ ์คํ์ด ๋๋ ๋๊น์ง ๋๊ธฐํ๋ ํจ์
Deferred ๊ฒฐ๊ณผ๋ ๋ฐํ
Copy 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 ๋๋ฏ๋ก ์ด๋ฅผ ํ์ธํ์ฌ ์๋์ผ๋ก ์ข
๋ฃ
Copy 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()
Copy 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์ฒ๋ผ ์ฐ๊ด๊ด๊ณ๋ฅผ ์ ์ํ๋ ค๋ฉด?
์ํฐํฐ ์์ฒด๋ฅผ ๋ฃ์ด์ฃผ๊ธฐ
Copy @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
EntityPrimaryKeyEntity.kt
Copy @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
Copy @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
Copy @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
Copy @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
๋ ์ ์๋๋ก ์ค์ ํ์
Copy kotlin("plugin.spring") version "1.7.0"
kotlin("plugin.jpa") version "1.7.0"
โ ๏ธ ํ์ง๋ง, ํ๋ฌ๊ทธ์ธ์ ์ถ๊ฐํ๋๋ผ๋ Entity Decompile์ ํด๋ณด๋ฉด final ํค์๋๊ฐ ์๋ค.
๊ทธ๋์ Hibernate ์ฌ์ฉ์ ์ํด Entity, Entity์ ์ธ์คํด์ค ๋ณ์๋ final์ด ์๋์ด์ผ ํ๋ฏ๋ก ์ถ๊ฐ ์ค์ ์ด ํ์
Copy 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 ๋ฏธํ์ฉ
Copy @Entity
@Table(name = "`user`")
class User(
name: String,
) : PrimaryKeyEntity() {
@Column(nullable = false, unique = true)
var name: String = name
protected set
}
โ
์์ฑ์ผ, ์์ ์ผ๊ณผ ๊ฐ์ด ๋ณ๊ฒฝ์ด ํ์ ์๋ ํ๋กํผํฐ
๋ค๋ฅธ ํ๋กํผํฐ์ฒ๋ผ setter์ ์ ๊ทผ์ ์ด๋ฅผ protected๋ก ์ ์ธ
๋ด๋ถ์ ์ผ๋ก๋ง ๋ณ๊ฒฝ์ ์ด์ด๋๊ธฐ์ ์ง์ ๊ฐ์ฒด ๋ด๋ถ์์ ๋ณ๊ฒฝ์ ํ์ง ์๋ ํ ์์
๊ฐ์ฒด ์์ฒด์์ ๋ณ๊ฒฝ์ ์๋ํ ์ ์์ง๋ง, ๋ถ๋ณ ํ๋กํผํฐ(immutable)๋ ๊ฐ๋ฐ์๊ฐ ๋ณ๊ฒฝ ํ๋กํผํฐ(mutable)๋ก ๋ฐ๊ฟ ์ ์๋ ๊ฒ์ ๋์ผํ๋ค๊ณ ํ๋จ
Copy @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
์์ฑ์ ๋ช
์ํ์ฌ ํ๋กํผํฐ์ ์์ฑ ํ์
์ ์๋ ค์ฃผ๋ฉด, ์์ ๊ฐ์ ๋ฐํ์ ์ค๋ฅ๋ฅผ ๋ง์์ฃผ๊ณ , ํ์
ํ๊ธฐ ์ฌ์์ง ์ ์๋ค
Copy @Column(nullable = false)
var title: String = title
protected set
(6) ์ธ๋ถ์ ๋
ธ์ถํ๋ ์ฐ๊ด๊ด๊ณ๋ Immutable Collection์ ๋
ธ์ถ
JPA์์ ์ฐ๊ด๊ด๊ณ์ ์์ ๋ณ๊ฒฝ์ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ๋ณ๊ฒฝ์ ์ ๋ฐํ์ฌ ํ๋กํผํฐ๋ฅผ ๋ถ๋ณ(val)์ผ๋ก ์ ์ธํด๋ ์/๋ณ์กฐ๊ฐ ๊ฐ๋ฅ
Copy // ์๋ณ๊ฐ ๊ฐ๋ฅํ MutableList
@OneToMany(fetch = FetchType.LAZY, cascade = [CascadeType.ALL], mappedBy = "writer")
val mutableBoards: MutableList<Board> = mutableListOf()
ํ์ง๋ง, ์ฐ๊ด๊ด๊ณ๋ฅผ ๋ณ๊ฒฝํ๋๊ฒ์ Entity์ ํน์ฑ์ ํ์ํ๊ธฐ ๋๋ฌธ์ MutableList๋ฅผ List๋ก ๋ฐ๊ฟ ์ ์๋ค. ๊ทธ๋ ๊ธฐ์ ๋ด๋ถ ์กฐ์์ฉ ํ๋กํผํฐ์ ์ธ๋ถ ๋
ธ์ถ์ฉ ํ๋กํผํฐ๋ฅผ ๋ณ๋๋ก ๋์ด ๊ด๋ฆฌํ ์ ์๋ค.
Copy @OneToMany(fetch = FetchType.LAZY, cascade = [CascadeType.ALL], mappedBy = "writer")
protected val mutableBoards: MutableList<Board> = mutableListOf()
val boards: List<Board> get() = mutableBoards // ์ธ๋ถ ๋
ธ์ถ์ฉ ํ๋กํผํฐ
boards๋ฅผ ์กฐํํ๋ ์์ ์ ์ฐธ์กฐ ์ฃผ์๊ฐ์ ๊ฐ์ง๊ณ ์์ผ๋ฏ๋ก ์ดํ ๊ฒ์ํ์ ์ถ๊ฐํ์๋ ๋ด์ฉ์ด ์ฐ๋
Copy @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?
Copy 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
Copy 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) ์ ๊ณต
โ
ํ๋กํผํฐ ๊ธฐ๋ฐ ํ
์คํธ
ํ๋กํผํฐ ๊ธฐ๋ฐ ํ
์คํธ๋ฅผ ์ง์
์์์ ์
๋ ฅ๊ฐ์ ๋ง๋ค์ด ์ฝ๋์ ์ ํจ์ฑ์ ๊ฒ์ฌํ๋ ๋ฐฉ์์ผ๋ก ๋ค์ํ ๊ฒฝ์ฐ์ ์๋ฅผ ์ฒด๊ณ์ ์ผ๋ก ํ
์คํธ
Copy class MyTests : PropertySpec({
forAll { a: Int, b: Int ->
(a + b) should beGreaterThan(a)
(a + b) should beGreaterThan(b)
}
})
โ
๋ฐ๋ณต ๋ฐ ์ค์ฒฉ ํ
์คํธ
๋ฐ๋ณต ๋ฐ ์ค์ฒฉ ํ
์คํธ๋ฅผ ์ง์ํ์ฌ ์ฌ๋ฌ ๋ณต์กํ ํ
์คํธ ์ผ์ด์ค๋ฅผ ๋ ์ฝ๊ณ ๊ฐ๊ฒฐํ๊ฒ ๊ด๋ฆฌ
Copy class MyTests : FunSpec({
context("Some context") {
test("Test 1") { /*...*/ }
test("Test 2") { /*...*/ }
}
})
Testing Style
https://kotest.io/docs/framework/testing-styles.html
โ
StringSpec
Copy class StringTest: StringSpec({
// ๋ฌธ์์ด์ JUnit์ @DisplayName์ ๋์ฒด
"strings.length should return size of string" {
"hello".length shouldBe 5
}
})
โ
FunSpec
Copy class FunSpecTest: FunSpec ({
test("๋ฌธ์์ด ๊ธธ์ด ํ
์คํธ") {
val actual = "abcdefg "
actual.length shouldBe 10
}
})
โ
AnnotationSpec
JUnit ํ
์คํธ ๋ฐฉ์๊ณผ ์ ์ฌ
Copy class AnnotationSpecTest: AnnotationSpec () {
@BeforeEach
fun beforeEach() {
println("start beforeEach")
}
@Test
fun stringTest() {
val actual = "abcdefg "
actual.length shouldBe 10
}
}
โ
DescribeSpec
describe
๊ฐ ํ
์คํธ ๋์์ ์ง์นญ
๋ด๋ถ์ ์ผ๋ก ์กฐ๊ฑด์ด๋ ์ํฉ์ ์ค๋ช
ํ ๋๋ context
์ฌ์ฉ
ํ
์คํธ ๋ณธ์ฒด์๋ it
์ ์ฌ์ฉํด์ ํ
์คํธ ์คํ ๋ฆฌํ
๋ง
Copy 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
์ฌ์ฉ
Copy 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 ์ ๋
ธํ
์ด์
์ ํ์ฉํ ๊ณ์ธต ๊ตฌ์กฐ ํ
์คํธ ๋ฐฉ์๊ณผ ์ ์ฌํ์ง๋ง ๋ ํธํ๊ฒ ์ฌ์ฉ ๊ฐ๋ฅ
Copy 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
๊ฒ์ฆ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์์ ์ ๊ณตํ๋ ์ฌ๋ฌ ๊ฒ์ฆ ํจ์
Copy 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
Copy /**
* 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
Copy @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์ ํ๋ก๊ทธ๋๋ฐ ์์
Copy [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 ์ ์ธํ ํ๋ก๊ทธ๋๋ฐ
๋ช
๋ นํ ํ๋ก๊ทธ๋๋ฐ
ํ๋ก๊ทธ๋๋ฐ์ ์ํ์ ์ํ๋ฅผ ๋ณ๊ฒฝ์ํค๋ ๊ตฌ๋ฌธ์ ๊ด์ ์ผ๋ก ์ ๊ทผํ๋ ํ๋ก๊ทธ๋๋ฐ ๋ฐฉ์.
์ปดํจํฐ๊ฐ ์คํํ ๋ช
๋ น๋ค์ ์คํ ์์๋๋ก ๊ตฌํ
๋๋ถ๋ถ์ ๊ฐ์ฒด ์งํฅ ํ๋ก๊ทธ๋๋ฐ ์ธ์ด๊ฐ ๋ช
๋ นํ ํ๋ก๊ทธ๋๋ฐ ์ธ์ด
์๊ณ ๋ฆฌ์ฆ ์ฒ๋ฆฌ ์์
์ ์ ํฉํ ์ธ์ด
์ ์ธํ ํ๋ก๊ทธ๋๋ฐ
์ ์ธ์ผ๋ก๋ง ํ๋ก๊ทธ๋จ์ ๋์์ํค๋ ๋ฐฉ์
ํ๋ก๊ทธ๋จ์ ์คํํ๊ธฐ ์ํด ๊ตฌ์ฒด์ ์ธ ์๋ ์์๋ฅผ ๋์ดํ์ง ์์
์์ ํ์ง ์์ง๋ง ํจ์ํ ํ๋ก๊ทธ๋๋ฐ์ ํ์ฉํด ์ผ์ ์์ค์ ์ ์ธํ ํ๋ก๊ทธ๋๋ฐ ๊ฐ๋ฅ
ํจ์ํ ํ๋ก๊ทธ๋๋ฐ์ ์ ์ธํ ํ๋ก๊ทธ๋๋ฐ์ ํ ์ข
๋ฅ
Copy /**
* ๋ช
๋ นํ ํ๋ก๊ทธ๋๋ฐ ์คํ์ผ
*/
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์ ๊ฒฐ๊ณผ)
Copy people.map(Person::name).filter { it.startWith("A") }
์ํ์ค๋ฅผ ์ฌ์ฉํ๋ฉด ์ค๊ฐ ์์ ์ปฌ๋ ์
์ ์ฌ์ฉํ์ง ์๊ณ ๋ ์ปฌ๋ ์
์ฐ์ฐ์ ์ฐ์
์์๊ฐ ๋ง์ ๊ฒฝ์ฐ ์ฑ๋ฅ์ด ๋์ ๋๊ฒ ํฅ์
์๋ฐ ์คํธ๋ฆผ๊ณผ ๋์ผํ ๊ฐ๋
Copy people.asSequence()
.map(Person::name)
.filter { it.startWith("A") }
.toList()
๐ก ํฐ ์ปฌ๋ ์
์ ๋ํด์ ์ฐ์ฐ์ ์ฐ์์ํฌ ๋๋ ์ํ์ค
๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์ ๊ท์น์ผ๋ก ์ผ์.
๐ก ์ํ์ค ์์๋ฅผ ์ธ๋ฑ์ค
๋ฅผ ์ฌ์ฉํด ์ ๊ทผํ๋ ๋ฑ์ ๋ค๋ฅธ API ๋ฉ์๋๊ฐ ํ์ํ๋ค๋ฉด ์ํ์ค๋ฅผ ๋ฆฌ์คํธ๋ก ๋ฐํ
ํ์.
Example
์ต๋ช
ํด๋์ค๋ฅผ ๋๋ค๋ก ์ ํํ๊ธฐ
Copy /**
* 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)
}
๋๋ค๋ฅผ ํ์ฉํด ์ค๋ณต ์ ๊ฑฐํ๊ธฐ
Copy /**
* 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)
Copy /**
* 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)
Copy @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)
Copy @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)
Copy val names = listOf("Aaron", "Park")
assertEquals("Aaron", names.get(0))
assertEquals("Aaron", names[0])
๋๋ค๋ฅผ ๊ดํธ ๋ฐ์ผ๋ก ๋นผ๋ด๋ ๊ด๋ก(Passing a lambda to the last parameter)
Copy @Test
fun `before`() {
assertThrows<IllegalStateException> {
check(false, { -> "Check failed." })
}
}
@Test
fun `after`() {
assertThrows<IllegalStateException> {
check(false) { "Check failed." }
}
}
์์ ๊ฐ์ฒด ์ง์ ๋๋ค(Lambda with receiver)
Copy @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 ์ค์ต
Copy 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"])
}
}