TDD, Clean Code with Kotlin Preview
κΈμ μ°Έκ³ νμ¬ μμ±ν κΈμ
λλ€.
Start Kotlin
Kotlin Web Compiler Site
λ³μμ μλ£ν
β
λ³μμ μ μΈ
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 μ½νλ¦°μ€λ½κ² μ¬μ©νκΈ°
π€·π»ββοΈ 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μ νΈνμ±μ κ°μ§λ©° μκ°μμΌλ‘ μ λ ¬ν μ μλ€λ νΉμ§
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
Junitκ³Ό Kotestμ μ°¨μ΄μ μ€ κ°μ₯ ν° λΆλΆμ κ°κ²°ν¨
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"])
}
}