πŸ“–
Aaron's TECH BOOK
  • Intro
    • About me
  • Lecture
    • Kubernetes
      • Begin Kubernetes
    • Kafka
      • Begin Kafka
    • Kotlin
      • TDD, Clean Code Preview
      • woowa Kotlin
    • Java
      • Multithread Concurrency
      • The Java
    • Toby
      • Toby Spring 6
      • Toby Spring Boot
    • MSA
      • 01.Micro Service
      • 02.DDD 섀계
      • 03.DDD κ΅¬ν˜„
      • 04.EDA κ΅¬ν˜„
    • Spring Boot
    • Spring Batch
    • Spring Core Advanced
    • Spring DB Part II
    • Spring DB Part I
    • JPA API and Performance Optimization
    • JPA Web Application
    • JPA Programming Basic
    • Spring MVC Part 2
      • 01.Thymeleaf
      • 02.ETC
      • 03.Validation
      • 04.Login
      • 05.Exception
    • Spring MVC Part 1
      • 01.Servlet
      • 02.MVC
    • Http
      • 01.Basic
      • 02.Method
      • 03.Header
    • Spring Core
    • Study
      • Concurrency issues
      • First Come First Served
      • Performance Test
      • TDD
      • IntelliJ
  • Book
    • Kafka Streams in Action
      • 01.μΉ΄ν”„μΉ΄ 슀트림즈
      • 02.μΉ΄ν”„μΉ΄ 슀트림즈 개발
      • 03.μΉ΄ν”„μΉ΄ 슀트림즈 관리
    • Effective Kotlin
      • 01.쒋은 μ½”λ“œ
      • 02.μ½”λ“œ 섀계
      • 03.νš¨μœ¨μ„±
    • 이벀트 μ†Œμ‹±κ³Ό MSA
      • 01.도메인 주도 섀계
      • 02.객체지ν–₯ 섀계 원칙
      • 03-04.이벀트 μ†Œμ‹±
      • 05.λ§ˆμ΄ν¬λ‘œμ„œλΉ„μŠ€ ν˜‘μ—…
      • 06.결과적 일관성
      • 07.CQRS
      • 08.UI
      • 09.ν΄λΌμš°λ“œ ν™˜κ²½
    • λͺ½κ³ DB μ™„λ²½ κ°€μ΄λ“œ
      • I. λͺ½κ³ DB μ‹œμž‘
      • II. λͺ½κ³ DB 개발
    • Kotlin Cookbook
      • μ½”ν‹€λ¦° 기초
      • μ½”ν‹€λ¦° κΈ°λŠ₯
      • ETC
    • Kotlin in Action
      • ν•¨μˆ˜/클래슀/객체/μΈν„°νŽ˜μ΄μŠ€
      • λžŒλ‹€μ™€ νƒ€μž…
      • μ˜€λ²„λ‘œλ”©κ³Ό κ³ μ°¨ ν•¨μˆ˜
      • μ œλ„€λ¦­μŠ€, μ• λ…Έν…Œμ΄μ…˜, λ¦¬ν”Œλ ‰μ…˜
    • Kent Beck Tidy First?
    • λŒ€κ·œλͺ¨ μ‹œμŠ€ν…œ 섀계 기초
      • 01.μ‚¬μš©μž μˆ˜μ— λ”°λ₯Έ 규λͺ¨ ν™•μž₯μ„±
      • 02.개랡적인 규λͺ¨ μΆ”μ •
      • 03.μ‹œμŠ€ν…œ 섀계 κ³΅λž΅λ²•
      • 04.처리율 μ œν•œ μž₯치 섀계
      • 05.μ•ˆμ • ν•΄μ‹œ 섀계
      • 06.ν‚€-κ°’ μ €μž₯μ†Œ 섀계
      • 07.유일 ID 생성기 섀계
      • 08.URL 단좕기 섀계
      • 09.μ›Ή 크둀러 섀계
      • 10.μ•Œλ¦Ό μ‹œμŠ€ν…œ 섀계
      • 11.λ‰΄μŠ€ ν”Όλ“œ μ‹œμŠ€ν…œ 섀계
      • 12.μ±„νŒ… μ‹œμŠ€ν…œ 섀계
      • 13.검색어 μžλ™μ™„μ„± μ‹œμŠ€ν…œ
      • 14.유튜브 섀계
      • 15.ꡬ글 λ“œλΌμ΄λΈŒ 섀계
      • 16.배움은 κ³„μ†λœλ‹€
    • μ‹€μš©μ£Όμ˜ ν”„λ‘œκ·Έλž˜λ¨ΈπŸ“–
    • GoF Design Patterns
    • 도메인 주도 개발 μ‹œμž‘ν•˜κΈ°
      • 01.도메인 λͺ¨λΈ μ‹œμž‘ν•˜κΈ°
      • 02.μ•„ν‚€ν…μ²˜ κ°œμš”
      • 03.μ• κ·Έλ¦¬κ±°νŠΈ
      • 04.리포지터리와 λͺ¨λΈ κ΅¬ν˜„
      • 05.Spring Data JPAλ₯Ό μ΄μš©ν•œ 쑰회 κΈ°λŠ₯
      • 06.μ‘μš© μ„œλΉ„μŠ€μ™€ ν‘œν˜„ μ˜μ—­
      • 07.도메인 μ„œλΉ„μŠ€
      • 08.μ• κ·Έλ¦¬κ±°νŠΈ νŠΈλžœμž­μ…˜ 관리
      • 09.도메인 λͺ¨λΈκ³Ό λ°”μš΄λ””λ“œ μ»¨ν…μŠ€νŠΈ
      • 10.이벀트
      • 11.CQRS
    • Effective Java 3/E
      • 객체, 곡톡 λ©”μ„œλ“œ
      • 클래슀, μΈν„°νŽ˜μ΄μŠ€, μ œλ„€λ¦­
    • μ†Œν”„νŠΈμ›¨μ–΄ μž₯인
    • ν•¨κ»˜ 자라기
    • Modern Java In Action
      • 01.기초
      • 02.ν•¨μˆ˜ν˜• 데이터 처리
      • 03.슀트림과 λžŒλ‹€λ₯Ό μ΄μš©ν•œ 효과적 ν”„λ‘œκ·Έλž˜λ°
      • 04.맀일 μžλ°”μ™€ ν•¨κ»˜
    • Refactoring
      • 01.λ¦¬νŽ™ν„°λ§ 첫 번째 μ˜ˆμ‹œ
      • 02.λ¦¬νŽ™ν„°λ§ 원칙
      • 03.μ½”λ“œμ—μ„œ λ‚˜λŠ” μ•…μ·¨
      • 06.기본적인 λ¦¬νŽ™ν„°λ§
      • 07.μΊ‘μŠν™”
      • 08.κΈ°λŠ₯ 이동
      • 09.데이터 쑰직화
      • 10.쑰건뢀 둜직 κ°„μ†Œν™”
      • 11.API λ¦¬νŒ©ν„°λ§
      • 12.상속 닀루기
    • 객체지ν–₯의 사싀과 μ˜€ν•΄
      • 01.ν˜‘λ ₯ν•˜λŠ” κ°μ²΄λ“€μ˜ 곡동체
      • 02.μ΄μƒν•œ λ‚˜λΌμ˜ 객체
      • 03.νƒ€μž…κ³Ό 좔상화
      • 04.μ—­ν• , μ±…μž„, ν˜‘λ ₯
      • 05.μ±…μž„κ³Ό λ©”μ‹œμ§€
      • 06.객체 지도
      • 07.ν•¨κ»˜ λͺ¨μœΌκΈ°
      • 뢀둝.좔상화 기법
    • Clean Code
    • μžλ°” ORM ν‘œμ€€ JPA ν”„λ‘œκ·Έλž˜λ°
Powered by GitBook
On this page
  • TDD, Clean Code with Kotlin Preview
  • Start Kotlin
  • λ³€μˆ˜μ™€ μžλ£Œν˜•
  • ν˜•λ³€ν™˜κ³Ό λ°°μ—΄
  • νƒ€μž…μΆ”λ‘ κ³Ό ν•¨μˆ˜
  • 쑰건문과 λΉ„κ΅μ—°μ‚°μž
  • 반볡문
  • 클래슀
  • ν”„λ‘œμ νŠΈ ꡬ쑰
  • μŠ€μ½”ν”„μ™€ μ ‘κ·Ό μ œν•œμž
  • κ³ μ°¨ν•¨μˆ˜μ™€ λžŒλ‹€ν•¨μˆ˜
  • μŠ€μ½”ν”„ ν•¨μˆ˜
  • Object
  • 읡λͺ…객체와 μ˜΅μ €λ²„ νŒ¨ν„΄
  • 클래슀의 λ‹€ν˜•μ„±
  • μ œλ„ˆλ¦­
  • 리슀트
  • λ¬Έμžμ—΄ 닀루기
  • Null μ²˜λ¦¬μ™€ 동일성확인
  • ν•¨μˆ˜μ˜ argumentλ₯Ό λ‹€λ£¨λŠ” 방법과 infix ν•¨μˆ˜
  • 쀑첩 ν΄λž˜μŠ€μ™€ λ‚΄λΆ€ 클래슀
  • Data Class & Enum Class
  • Set & Map
  • μ»¬λ ‰μ…˜ ν•¨μˆ˜
  • λ³€μˆ˜μ˜ κ³ κΈ‰ 기술
  • 코루틴을 ν†΅ν•œ 비동기 처리
  • Kotlin Steps
  • JPA Entity μ½”ν‹€λ¦°μŠ€λŸ½κ²Œ μ‚¬μš©ν•˜κΈ°
  • Kotest
  • μ½”ν‹€λ¦° 기초, λ‹¨μœ„ ν…ŒμŠ€νŠΈ
  • TDD
  • FP, μ½”ν‹€λ¦° DSL
  1. Lecture
  2. Kotlin

TDD, Clean Code Preview

Last updated 8 months ago

TDD, Clean Code with Kotlin Preview

글을 μ°Έκ³ ν•˜μ—¬ μž‘μ„±ν•œ κΈ€μž…λ‹ˆλ‹€.

Start Kotlin

Kotlin Web Compiler Site


λ³€μˆ˜μ™€ μžλ£Œν˜•

βœ… λ³€μˆ˜μ˜ μ„ μ–Έ

var: 일반적으둜 ν†΅μš©λ˜λŠ” λ³€μˆ˜. μ–Έμ œλ“ μ§€ 읽기 μ“°κΈ°κ°€ κ°€λŠ₯ val: μ„ μ–Έμ‹œμ—λ§Œ μ΄ˆκΈ°ν™” κ°€λŠ₯. 쀑간에 κ°’ λ³€κ²½ λΆˆκ°€

fun main() {
    var a: Int
    a = 123
    println(a) // 123
    
    var b: Int? = null // nallable λ³€μˆ˜
    b = null
    println(b) // null
}

λ³€μˆ˜μ˜ μ„ μ–Έ μœ„μΉ˜μ— λ”°λ₯Έ 이름

  • Property: ν΄λž˜μŠ€μ— μ„ μ–Έλœ λ³€μˆ˜

  • Local Variable: μ΄μ™Έμ˜ Scope 내에 μ„ μ–Έλœ λ³€μˆ˜

| 코틀린은 κΈ°λ³Έ λ³€μˆ˜μ—μ„œ null을 ν—ˆμš©ν•˜μ§€ μ•ŠλŠ”λ‹€.

  • λ³€μˆ˜μ— 값을 ν• λ‹Ήν•˜μ§€ μ•Šμ€μ±„λ‘œ μ‚¬μš©ν•˜κ²Œ 되면 컴파일 μ—λŸ¬

βœ… μ½”ν‹€λ¦°μ˜ κΈ°λ³Έ μžλ£Œν˜•

μžλ°”μ™€μ˜ ν˜Έν™˜μ„ μœ„ν•΄ μžλ°”μ™€ 거의 동일

fun main() {
    // μ •μˆ˜ν˜•(8μ§„μˆ˜ ν‘œκΈ°λŠ” 미지원)
    var intValue:Int = 1234 // 32λΉ„νŠΈ μ΄λ‚΄μ˜ 10μ§„μˆ˜
    var longValue:Long = 1234L // 64λΉ„νŠΈ Longνƒ€μž… 10μ§„μˆ˜
    var intValueByHex:Int = 0x1af // 16μ§„μˆ˜
    var intValueByBin:Int = 0b10110110 // 2μ§„μˆ˜
    
    // μ‹€μˆ˜ν˜•
    var doubleValue:Double = 123.5 // μ‹€μˆ˜μ˜ κΈ°λ³Έ
    var doubleValueWithExp:Double = 123.5e10 // ν•„μš” μ‹œ μ§€μˆ˜ ν‘œκΈ°λ²• μΆ”κ°€
    var floatValue:Float = 123.5f // 16λΉ„νŠΈ float

    // λ¬Έμžν˜•(λ‚΄λΆ€μ μœΌλ‘œ λ¬Έμžμ—΄μ„ UTF-16 BE둜 관리. κΈ€μž ν•˜λ‚˜κ°€ 2bytes λ©”λͺ¨λ¦¬ 곡간 μ‚¬μš©)
    var charValue:Char = 'a'
    var koreanCharValue:Char = 'κ°€'
    
    // λ…Όλ¦¬ν˜•
    var booleanValue:Boolean = true
    
    // λ¬Έμžμ—΄
    val stringValue = "one line string test"
    val multiLineStringValue = """multiline
    string
    test"""
}

μ§€μ›λ˜λŠ” 특수문자


ν˜•λ³€ν™˜κ³Ό λ°°μ—΄

βœ… ν˜•λ³€ν™˜

코틀린은 ν˜•λ³€ν™˜ μ‹œ λ°œμƒν•  수 μžˆλŠ” 였λ₯˜λ₯Ό 막기 μœ„ν•΄ μ•”μ‹œμ  ν˜•λ³€ν™˜μ€ 미지원

fun main() {
    // λͺ…μ‹œμ  ν˜•λ³€ν™˜
    var a: Int = 54321
    var b: Long = a.toLong()
}

βœ… λ°°μ—΄

arrayOf, arrayOfNulls

fun main() {
    // 값이 μžˆλŠ” λ°°μ—΄ 생성
    var intArr = arrayOf(1, 2, 3, 4, 5)
    
    // νŠΉμ • 크기λ₯Ό κ°€μ§„ λΉ„μ–΄μžˆλŠ” λ°°μ—΄ 생성
    var nullArr = arrayOfNulls<Int>(5)
    
    intArr[2] = 8
    println(intArr[4])
}

νƒ€μž…μΆ”λ‘ κ³Ό ν•¨μˆ˜

βœ… νƒ€μž…μΆ”λ‘ 

λ³€μˆ˜ ν•¨μˆ˜λ“€μ„ μ„ μ–Έν•  λ•Œλ‚˜ 연산이 μ΄λ£¨μ–΄μ§ˆ λ•Œ μžλ£Œν˜•μ„ μ½”λ“œμ— λͺ…μ‹œν•˜μ§€ μ•Šμ•„λ„ μžλ™μœΌλ‘œ μžλ£Œν˜•μ„ μΆ”λ‘ 

  • λ°˜λ“œ νŠΉμ •ν•œ μžλ£Œν˜•μœΌλ‘œ μ§€μ •ν•΄μ•Όν•˜λŠ” 상황이 μ•„λ‹ˆλΌλ©΄ λŒ€λΆ€λΆ„μ€ μ½”ν‹€λ¦°μ˜ νƒ€μž…μΆ”λ‘  κΈ°λŠ₯을 이용

βœ… ν•¨μˆ˜

μ½”ν‹€λ¦°μ—μ„œ ν•¨μˆ˜λŠ” λ‚΄λΆ€μ μœΌλ‘œ κΈ°λŠ₯을 κ°€μ§„ ν˜•νƒœμ§€λ§Œ, μ™ΈλΆ€μ—μ„œ λ³Ό λ•ŒλŠ” νŒŒλΌλ―Έν„°λ₯Ό λ„£λŠ”λ‹€λŠ” 점 μ™Έμ—λŠ” μžλ£Œν˜•μ΄ κ²°μ •λœ λ³€μˆ˜λΌλŠ” κ°œλ…μœΌλ‘œ μ ‘κ·Ό

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

// ν•¨μˆ˜
fun add(a: Int, b: Int, c: Int): Int {
    return a + b + c
}

// λ‹¨μΌν‘œν˜„μ‹(λ°˜ν™˜ν˜• νƒ€μž… μΆ”λ‘ )
fun add2(a: Int, b: Int, c: Int) = a + b + c

쑰건문과 λΉ„κ΅μ—°μ‚°μž

βœ… 쑰건문

if

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

when

  • λ“±ν˜Έλ‚˜ λΆ€λ“±ν˜ΈλŠ” μ‚¬μš© λΆˆκ°€

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

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

βœ… λΉ„κ΅μ—°μ‚°μž

  • λΆ€λ“±ν˜Έ: <, ≀, >, β‰₯, β‰ 

  • λ“±ν˜Έ: ==

  • μžλ£Œν˜• 확인: is, !is

    • ν˜Έν™˜ μ—¬λΆ€λ₯Ό μ²΄ν¬ν•˜κ³  ν˜•λ³€ν™˜κΉŒμ§€ ν•œλ²ˆμ— μ§„ν–‰

    a is Int

반볡문

λ‹€λ₯Έ μ–Έμ–΄μ—μ„œμ˜ λ°˜λ³΅λ¬Έκ³ΌλŠ” μ•½κ°„μ˜ 차이가 μžˆλ‹€.

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

λ ˆμ΄λΈ”μ΄ 달린 반볡문 κΈ°μ€€μœΌλ‘œ λ°˜λ³΅λ¬Έμ„ μ’…λ£Œμ‹œμΌœμ£ΌλŠ” κΈ°λŠ₯

  • λ ˆμ΄λΈ” 이름과 @기호둜 μ¦‰μ‹œ 반볡문 μ’…λ£Œ

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

클래슀

fun main() {
	var a = Person("λ°•λ³΄μ˜", 1990)
    var b = Person("μ „μ •κ΅­", 1997)
    var c = Person("μž₯μ›μ˜", 2004)
    
    println("μ•ˆλ…•ν•˜μ„Έμš”. ${a.birthYear}년생 ${a.name}μž…λ‹ˆλ‹€.")
    
    b.introduce()
    c.introduce()
    
    var d = Person("이루닀")
    var e = Person("μ°¨μ€μš°")
    var f = Person("λ₯˜μˆ˜μ •")
}

class Person(var name:String, val birthYear:Int) { // 클래슀의 속성듀을 선언함과 λ™μ‹œμ— μƒμ„±μžλ₯Ό μ„ μ–Έν•˜λŠ” 방법
    /** init
     * μƒμ„±μžλ₯Ό 톡해 μΈμŠ€ν„΄μŠ€κ°€ λ§Œλ“€μ–΄μ§ˆ λ•Œ ν˜ΈμΆœλ˜λŠ” ν•¨μˆ˜
     */
    init {
        println("[init] ${this.birthYear}년생 ${this.name}λ‹˜μ˜ μΈμŠ€ν„΄μŠ€κ°€ μƒμ„±λ˜μ—ˆμŠ΅λ‹ˆλ‹€.")        
    }
    
    /** 보쑰 μƒμ„±μž
     * 보쑰 μƒμ„±μžλ₯Ό λ§Œλ“€ 경우 λ°˜λ“œμ‹œ κΈ°λ³Έ μƒμ„±μžλ₯Ό 톡해 속성을 μ΄ˆκΈ°ν™”
     */
    constructor(name:String) : this(name, 1997) {
        println("[constructor] 보쑰 μƒμ„±μžκ°€ μ‚¬μš©λ˜μ—ˆμŠ΅λ‹ˆλ‹€.")
        
    }
    
    fun introduce() {
        println("[introduce] μ•ˆλ…•ν•˜μ„Έμš”. ${birthYear}년생 ${name}μž…λ‹ˆλ‹€.")
    }
}
[init] 1990년생 λ°•λ³΄μ˜λ‹˜μ˜ μΈμŠ€ν„΄μŠ€κ°€ μƒμ„±λ˜μ—ˆμŠ΅λ‹ˆλ‹€.
[init] 1997년생 μ „μ •κ΅­λ‹˜μ˜ μΈμŠ€ν„΄μŠ€κ°€ μƒμ„±λ˜μ—ˆμŠ΅λ‹ˆλ‹€.
[init] 2004년생 μž₯μ›μ˜λ‹˜μ˜ μΈμŠ€ν„΄μŠ€κ°€ μƒμ„±λ˜μ—ˆμŠ΅λ‹ˆλ‹€.
μ•ˆλ…•ν•˜μ„Έμš”. 1990년생 λ°•λ³΄μ˜μž…λ‹ˆλ‹€.
[introduce] μ•ˆλ…•ν•˜μ„Έμš”. 1997년생 μ „μ •κ΅­μž…λ‹ˆλ‹€.
[introduce] μ•ˆλ…•ν•˜μ„Έμš”. 2004년생 μž₯μ›μ˜μž…λ‹ˆλ‹€.
[init] 1997년생 μ΄λ£¨λ‹€λ‹˜μ˜ μΈμŠ€ν„΄μŠ€κ°€ μƒμ„±λ˜μ—ˆμŠ΅λ‹ˆλ‹€.
[constructor] 보쑰 μƒμ„±μžκ°€ μ‚¬μš©λ˜μ—ˆμŠ΅λ‹ˆλ‹€.
[init] 1997년생 μ°¨μ€μš°λ‹˜μ˜ μΈμŠ€ν„΄μŠ€κ°€ μƒμ„±λ˜μ—ˆμŠ΅λ‹ˆλ‹€.
[constructor] 보쑰 μƒμ„±μžκ°€ μ‚¬μš©λ˜μ—ˆμŠ΅λ‹ˆλ‹€.
[init] 1997년생 λ₯˜μˆ˜μ •λ‹˜μ˜ μΈμŠ€ν„΄μŠ€κ°€ μƒμ„±λ˜μ—ˆμŠ΅λ‹ˆλ‹€.
[constructor] 보쑰 μƒμ„±μžκ°€ μ‚¬μš©λ˜μ—ˆμŠ΅λ‹ˆλ‹€.

상속

fun main() {
	var a = Animal("별이", 5, "개")
    var b = Dog("별이", 5)
    
    a.introduce()
    b.introduce()
    
    b.bark()
    
    var c = Cat("루이", 1)
    
    c.introduce()
    c.meow()
}

/** open
 * ν΄λž˜μŠ€κ°€ 상속될 수 μžˆλ„λ‘ ν—ˆμš©ν•˜λŠ” ν‚€μ›Œλ“œ
 */
open class Animal (var name:String, var age:Int, var type:String) {
    fun introduce() {
        println("μ €λŠ” ${type} ${name}이고, ${age}μ‚΄ μž…λ‹ˆλ‹€.")
    }
}

/** 상속 κ·œμΉ™
 * 1. μ„œλΈŒ ν΄λž˜μŠ€λŠ” 수퍼 ν΄λž˜μŠ€μ— μ‘΄μž¬ν•˜λŠ” 속성과 같은 μ΄λ¦„μ˜ 속성을 κ°€μ§ˆ 수 μ—†λ‹€.
 * 2. μ„œλΈŒ ν΄λž˜μŠ€κ°€ 생성될 λ•Œ λ°˜λ“œμ‹œ 수퍼클래슀의 μƒμ„±μžκΉŒμ§€ ν˜ΈμΆœλ˜μ–΄μ•Ό ν•œλ‹€.
 */
 class Dog (name:String, age:Int) : Animal (name, age, "개") {
     fun bark() {
         println("멍멍")
     }
 }
 
class Cat (name:String, age:Int) : Animal (name, age, "고양이") {
     fun meow() {
         println("μ•Όμ˜Ήμ•Όμ˜Ή")
     }
}

μ˜€λ²„λΌμ΄λ”©

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

/** 
 * 상속이 κ°€λŠ₯ν•˜λ„λ‘ open 된 클래슀
 */
open class Animal () {
    // 수퍼 ν΄λž˜μŠ€μ—μ„œ open 된 ν•¨μˆ˜λŠ” μ„œλΈŒ ν΄λž˜μŠ€μ—μ„œ override κ°€λŠ₯
    open fun eat() {
        println("μŒμ‹μ„ λ¨ΉμŠ΅λ‹ˆλ‹€")
    }
}

class Tiger : Animal() {
    override fun eat() {
        println("κ³ κΈ°λ₯Ό λ¨ΉμŠ΅λ‹ˆλ‹€")
    }
}

좔상화

좔상 클래슀: 좔상 ν•¨μˆ˜λ₯Ό ν¬ν•¨ν•˜λŠ” 클래슀

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

// 좔상 클래슀
abstract class Animal () {
    abstract fun eat() // 좔상 ν•¨μˆ˜
    fun sniff() {
        println("킁킁")
    }
}

class Rabbit : Animal() {
    override fun eat() {
        println("당근을 λ¨ΉμŠ΅λ‹ˆλ‹€")
    }
}

μΈν„°νŽ˜μ΄μŠ€: 속성, μΆ”μƒν•¨μˆ˜, μΌλ°˜ν•¨μˆ˜ 포함

  • κ΅¬ν˜„λΆ€κ°€ μžˆλŠ” ν•¨μˆ˜ β†’ open ν•¨μˆ˜λ‘œ κ°„μ£Ό

  • κ΅¬ν˜„λΆ€κ°€ μ—†λŠ” ν•¨μˆ˜ β†’ abstract ν•¨μˆ˜λ‘œ κ°„μ£Ό

  • ⚠️ μ—¬λŸ¬κ°œμ˜ μΈν„°νŽ˜μ΄μŠ€λ‚˜ ν΄λž˜μŠ€μ—μ„œ 같은 이름과 ν˜•νƒœλ₯Ό κ°€μ§„ ν•¨μˆ˜λ₯Ό κ΅¬ν˜„ν•˜κ³  μžˆλ‹€λ©΄,

    • μ„œλΈŒν΄λž˜μŠ€μ—μ„œλŠ” ν˜Όμ„ μ΄ μΌμ–΄λ‚˜μ§€ μ•Šλ„λ‘ λ°˜λ“œμ‹œ μ˜€λ²„λΌμ΄λ”©ν•˜μ—¬ μž¬κ΅¬ν˜„ ν•„μš”

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

interface Runner {
    fun run()
}

interface Eater {
    fun eat() {
        println("μŒμ‹μ„ λ¨ΉμŠ΅λ‹ˆλ‹€")
    }
}

class Dog : Runner, Eater {
    override fun run() {
        println("μš°λ‹€λ‹€λ‹€ λœλ‹ˆλ‹€")
    }
    
    override fun eat() {
        println("ν—ˆκ²μ§€κ² λ¨ΉμŠ΅λ‹ˆλ‹€")
    }
}

ν”„λ‘œμ νŠΈ ꡬ쑰

  • Project

    • λͺ¨λ“  λ‚΄μš©μ„ λ‹΄λŠ” 큰 ν‹€

  • Module

    • ν•˜λ‚˜μ˜ ν”„λ‘œμ νŠΈλŠ” μ—¬λŸ¬κ°œμ˜ λͺ¨λ“ˆλ‘œ μ΄λ£¨μ–΄μ§ˆ 수 μžˆλ‹€.

      • λͺ¨λ“ˆμ€ 직접 λ§Œλ“€ μˆ˜λ„ 있고, ν•„μš”ν•œ κΈ°λŠ₯을 미리 κ΅¬ν˜„ν•΄ λ‘” 라이브러리 λͺ¨λ“ˆμ„ 가져와 μ‚¬μš© κ°€λŠ₯

    • λͺ¨λ“ˆ μ•ˆμ—λŠ” λ‹€μˆ˜μ˜ 폴더(kt, λͺ¨λ“ˆ κ΄€λ ¨ μ„€μ •, λ¦¬μ†ŒμŠ€ 파일 λ“±)와 파일이 쑴재

  • Package

    • μ†ŒμŠ€ μ½”λ“œμ˜ μ†Œμ†μ„ μ§€μ •ν•˜κΈ° μœ„ν•œ 논리적 λ‹¨μœ„

    • 일반적으둜 νŒ¨ν‚€μ§€ 이름을 지을 λ•Œ νšŒμ‚¬ 도메인을 거꾸둜 ν•΄μ„œ ν”„λ‘œμ νŠΈλͺ…κ³Ό μ„ΈλΆ€ κΈ°λŠ₯을 λΆ™μ΄λŠ” 방식

      • com.youtube.aaron

      • com.youtube.aaron.base

      • com.youtube.aaron.kotlin

      • com.youtube.aaron.talk

    • 코틀린은 μžλ°”μ™€ 달리 폴더 ꡬ쑰와 νŒ¨ν‚€μ§€ λͺ…을 μΌμΉ˜μ‹œν‚€μ§€ μ•Šμ•„λ„ λœλ‹€.

      • λ‹¨μˆœνžˆ 파일 상단에 νŒ¨ν‚€μ§€λ§Œ λͺ…μ‹œν•΄ μ£Όλ©΄ μ»΄νŒŒμΌλŸ¬κ°€ μ•Œμ•„μ„œ 처리

코틀린은 클래슀λͺ…κ³Ό 파일λͺ…이 μΌμΉ˜ν•˜μ§€ μ•Šμ•„λ„ 되며,

ν•˜λ‚˜μ˜ νŒŒμΌμ— μ—¬λŸ¬κ°œμ˜ 클래슀λ₯Ό 넣어도 μ•Œμ•„μ„œ 컴파일 κ°€λŠ₯

  • νŒŒμΌμ΄λ‚˜ 폴더 κΈ°μ€€μœΌλ‘œ κ΅¬λΆ„ν•˜μ§€ μ•Šκ³  νŒŒμΌλ‚΄μ— μžˆλŠ” package ν‚€μ›Œλ“œ κΈ°μ€€μœΌλ‘œ ꡬ뢄


μŠ€μ½”ν”„μ™€ μ ‘κ·Ό μ œν•œμž

βœ… μŠ€μ½”ν”„

νŒ¨ν‚€μ§€ μ•ˆμ— λ³€μˆ˜, ν•¨μˆ˜, ν΄λž˜μŠ€λŠ” λͺ¨λ‘ ν•˜λ‚˜μ˜ μŠ€μ½”ν”„μ— μžˆλŠ” 멀버

ν•¨μˆ˜, ν΄λž˜μŠ€μ•ˆμ— λ˜λ‹€λ₯Έ λ³€μˆ˜, ν•¨μˆ˜κ°€ μ‘΄μž¬ν•œλ‹€λ©΄ νŒ¨ν‚€μ§€ μ•ˆμ— λ˜λ‹€λ₯Έ ν•˜μœ„ μŠ€μ½”ν”„λ‘œ λ™μž‘

μŠ€μ½”ν”„μ— λŒ€ν•œ μ„Έ κ°€μ§€ κ·œμΉ™

  • (1) μŠ€μ½”ν”„ μ™ΈλΆ€μ—μ„œλŠ” μŠ€μ½”ν”„ λ‚΄λΆ€μ˜ 멀버λ₯Ό μ°Έμ‘°μ—°μ‚°μžλ‘œλ§Œ μ°Έμ‘° κ°€λŠ₯

a.eat()
import com.google.aaron
import com.google.aaron.A
  • (2) 동일 μŠ€μ½”ν”„ λ‚΄μ—μ„œλŠ” 멀버듀을 κ³΅μœ ν•  수 있음

  • (3) ν•˜μœ„ μŠ€μ½”ν”„μ—μ„œλŠ” μƒμœ„ μŠ€μ½”ν”„μ˜ 멀버λ₯Ό μž¬μ •μ˜ κ°€λŠ₯

βœ… μ ‘κ·Ό μ œν•œμž

λ³€μˆ˜, ν•¨μˆ˜, 클래슀 μ„ μ–Έ μ‹œ 맨 μ•žμ— λΆ™μ—¬ μ‚¬μš©

μŠ€μ½”ν”„ μ™ΈλΆ€μ—μ„œ μŠ€μ½”ν”„ 내뢀에 μ ‘κ·Όν•  λ•Œ κ·Έ κΆŒν•œμ„ κ°œλ°œμžκ°€ μ œμ–΄ν•  수 μžˆλŠ” κΈ°λŠ₯

  • public

  • internal

  • private

  • protected

Package Scope

public (default)
μ–΄λ–€ νŒ¨ν‚€μ§€μ—μ„œλ„ μ ‘κ·Ό κ°€λŠ₯

internal

같은 λͺ¨λ“ˆ λ‚΄μ—μ„œλ§Œ μ ‘κ·Ό κ°€λŠ₯

private

같은 파일 λ‚΄μ—μ„œλ§Œ μ ‘κ·Ό κ°€λŠ₯

protected

λ―Έμ‚¬μš©

Class Scope

public (default)
클래슀 μ™ΈλΆ€μ—μ„œ 항상 μ ‘κ·Ό κ°€λŠ₯

private

클래슀 λ‚΄λΆ€μ—μ„œλ§Œ μ ‘κ·Ό κ°€λŠ₯

protected

클래슀 μžμ‹ κ³Ό 상속받은 ν΄λž˜μŠ€μ—μ„œ μ ‘κ·Ό κ°€λŠ₯

internal

λ―Έμ‚¬μš©


κ³ μ°¨ν•¨μˆ˜μ™€ λžŒλ‹€ν•¨μˆ˜

βœ… κ³ μ°¨ν•¨μˆ˜

ν•¨μˆ˜λ₯Ό 마치 ν΄λž˜μŠ€μ—μ„œ λ§Œλ“€μ–΄λ‚Έ μΈμŠ€ν„΄μŠ€μ²˜λŸΌ μ·¨κΈ‰ν•˜λŠ” 방법

  • ν•¨μˆ˜λ₯Ό νŒŒλΌλ―Έν„°λ‘œ λ„˜κ²¨μ€„ μˆ˜λ„ 있고, κ²°κ³Όκ°’μœΌλ‘œ λ°˜ν™˜λ°›μ„ μˆ˜λ„ μžˆλŠ” 방법

μ½”ν‹€λ¦°μ—μ„œλŠ” λͺ¨λ“  ν•¨μˆ˜λ₯Ό κ³ μ°¨ν•¨μˆ˜λ‘œ μ‚¬μš© κ°€λŠ₯

  • :: β†’ 일반 ν•¨μˆ˜λ₯Ό κ³ μ°¨ ν•¨μˆ˜λ‘œ λ³€κ²½ν•΄ μ£ΌλŠ” μ—°μ‚°μž

  • ν•¨μˆ˜λ₯Ό νŒŒλΌλ―Έν„°λ‘œ 받을 경우 νƒ€μž…μ€ ν•¨μˆ˜μ˜ (νŒŒλΌλ―Έν„° μžλ£Œν˜•) -> λ°˜ν™˜ν˜• μžλ£Œν˜•

fun main() {
    b(::a) // 일반 ν•¨μˆ˜λ₯Ό κ³ μ°¨ ν•¨μˆ˜λ‘œ λ³€κ²½
}

fun a (str: String) {
    println("$str ν•¨μˆ˜ a")
}

// ν•¨μˆ˜λ₯Ό νŒŒλΌλ―Έν„°λ‘œ λ°›κΈ°. (νŒŒλΌλ―Έν„° μžλ£Œν˜•) -> λ°˜ν™˜ν˜• μžλ£Œν˜•
fun b (function: (String)->Unit) {
    function("bκ°€ ν˜ΈμΆœν•œ")
}

βœ… λžŒλ‹€ν•¨μˆ˜

λžŒλ‹€ν•¨μˆ˜λŠ” μΌλ°˜ν•¨μˆ˜μ™€ 달리 κ·Έ μžμ²΄κ°€ κ³ μ°¨ν•¨μˆ˜μ΄λ―€λ‘œ λ³„λ„μ˜ μ—°μ‚°μž 없이 λ³€μˆ˜μ— 담을 수 μžˆλ‹€.

fun main() {
    /** μžλ£Œν˜• μžλ™ μΆ”λ‘ μœΌλ‘œ μΆ•μ•½ μ‚¬μš©
     * var c: (String) -> Unit = { str:String -> println("$str ν•¨μˆ˜ a")}
     * var c: (String) -> Unit = { str -> println("$str ν•¨μˆ˜ a")}
     */
    var c = { str:String -> println("$str ν•¨μˆ˜ a")}
    b(c)
}

fun b (function: (String)->Unit) {
    function("bκ°€ ν˜ΈμΆœν•œ")
}

ℹ️ κ³ μ°¨ν•¨μˆ˜μ™€ λžŒλ‹€ν•¨μˆ˜λ₯Ό μ‚¬μš©ν•˜μ—¬ ν•¨μˆ˜λ₯Ό μΌμ’…μ˜ λ³€μˆ˜λ‘œ μ‚¬μš© κ°€λŠ₯ν•œ νŽΈμ˜μ„±

  • μ»¬λ ‰μ…˜ μ‘°μž‘μ΄λ‚˜ μŠ€μ½”ν”„ ν•¨μˆ˜μ—λ„ 도움


μŠ€μ½”ν”„ ν•¨μˆ˜

ν•¨μˆ˜ν˜• μ–Έμ–΄μ˜ νŠΉμ§•μ„ 더 νŽΈλ¦¬ν•˜κ²Œ μ‚¬μš©ν•  수 μžˆλ„λ‘ κΈ°λ³Έ μ œκ³΅ν•˜λŠ” ν•¨μˆ˜λ“€

ν΄λž˜μŠ€μ—μ„œ μƒμ„±ν•œ μΈμŠ€ν„΄μŠ€λ₯Ό μŠ€μ½”ν”„ ν•¨μˆ˜μ— μ „λ‹¬ν•˜λ©΄,

  • μΈμŠ€ν„΄μŠ€μ˜ μ†μ„±μ΄λ‚˜ ν•¨μˆ˜λ₯Ό μ’€ 더 κΉ”λ”ν•˜κ²Œ 뢈러 μ“Έ 수 μžˆλ‹€.

πŸ“¦ apply

μΈμŠ€ν„΄μŠ€ 생성 ν›„ λ³€μˆ˜μ— λ‹΄κΈ° μ „ μ΄ˆκΈ°ν™” 과정을 μˆ˜ν–‰ν•  λ•Œ 주둜 μ‚¬μš©

  • apply의 scope μ•ˆμ—μ„œ 직접 μΈμŠ€ν„΄μŠ€μ˜ 속성과 ν•¨μˆ˜λ₯Ό μ°Έμ‘°μ—°μ‚°μž 없이 μ‚¬μš© κ°€λŠ₯

    • λ˜ν•œ μΈμŠ€ν„΄μŠ€ μžμ‹ μ„ λ‹€μ‹œ λ°˜ν™˜ν•˜λ―€λ‘œ μƒμ„±λ˜μžλ§ˆμž μ‘°μž‘λœ μΈμŠ€ν„΄μŠ€λ₯Ό λ³€μˆ˜μ— λ°”λ‘œ μ΄ˆκΈ°ν™” κ°€λŠ₯

fun main() {
    var a = Book("μ½”ν‹€λ¦° κ°•μ˜", 10000).apply {
        name = "[μ΄ˆνŠΉκ°€] " + name
        discount()
    }
    println(a.name + ", " + a.price) // [μ΄ˆνŠΉκ°€] μ½”ν‹€λ¦° κ°•μ˜, 8000
}

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

πŸ“¦ run

μΈμŠ€ν„΄μŠ€κ°€ λ§Œλ“€μ–΄μ§„ 후에 μΈμŠ€ν„΄μŠ€μ˜ ν•¨μˆ˜λ‚˜ 속성을 μŠ€μ½”ν”„ λ‚΄μ—μ„œ μ‚¬μš©ν•΄μ•Ό ν•  경우 유용

  • apply와 λ™μΌν•˜κ²Œ μŠ€μ½”ν”„ μ•ˆμ—μ„œ μ°Έμ‘°μ—°μ‚°μžλ₯Ό μ‚¬μš©ν•˜μ§€ μ•Šμ•„λ„ λœλ‹€λŠ” 점은 κ°™μ§€λ§Œ, 일반 λžŒλ‹€ν•¨μˆ˜μ²˜λŸΌ μΈμŠ€ν„΄μŠ€λŒ€μ‹  결과값을 λ°˜ν™˜

fun main() {
    var a = Book("μ½”ν‹€λ¦° κ°•μ˜", 10000).apply {
        name = "[μ΄ˆνŠΉκ°€] " + name
        discount()
    }
    println(a.name + ", " + a.price) // [μ΄ˆνŠΉκ°€] μ½”ν‹€λ¦° κ°•μ˜, 8000
    
    a.run {
        println("μƒν’ˆλͺ…: ${name}, 가격: ${price}원") // μƒν’ˆλͺ…: [μ΄ˆνŠΉκ°€] μ½”ν‹€λ¦° κ°•μ˜, 가격: 8000원
    }
}

πŸ“¦ with

  • runκ³Ό λ™μΌν•œ κΈ°λŠ₯을 κ°€μ§€μ§€λ§Œ, μΈμŠ€ν„΄μŠ€λ₯Ό μ°Έμ‘°μ—°μ‚°μž λŒ€μ‹  νŒŒλΌλ―Έν„°λ‘œ λ°›λŠ”λ‹€λŠ” 차이

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

...

fun main() {
    
    var a = Book("μ½”ν‹€λ¦° κ°•μ˜", 10000).apply {
        name = "[μ΄ˆνŠΉκ°€] " + name
        discount()
    }

    with(a) {
        println("μƒν’ˆλͺ…: ${name}, 가격: ${price}원") // μƒν’ˆλͺ…: [μ΄ˆνŠΉκ°€] μ½”ν‹€λ¦° κ°•μ˜, 가격: 8000원
    }
}

πŸ“¦ also

  • apply와 μœ μ‚¬ν•˜κ²Œ μ²˜λ¦¬κ°€ λλ‚˜λ©΄ μΈμŠ€ν„΄μŠ€λ₯Ό λ°˜ν™˜ν•˜μ§€λ§Œ, νŒŒλΌλ―Έν„°λ‘œ μΈμŠ€ν„΄μŠ€λ₯Ό λ„˜κΈ΄ 것과 같이 it을 톡해 μΈμŠ€ν„΄μŠ€λ₯Ό μ‚¬μš©

    • 같은 μ΄λ¦„μ˜ λ³€μˆ˜λ‚˜ ν•¨μˆ˜κ°€ μŠ€μ½”ν”„ λ°”κΉ₯에 μ€‘λ³΅λ˜μ–΄ μžˆλŠ” 경우 ν˜Όλž€μ„ λ°©μ§€ν•˜κΈ° μœ„ν•¨

fun main() {

    var a = Book("μ½”ν‹€λ¦° κ°•μ˜", 10000).also {
        it.name = "[μ΄ˆνŠΉκ°€] " + it.name
        it.discount()
    }

    a.run {
        println("μƒν’ˆλͺ…: ${name}, 가격: ${price}원") // μƒν’ˆλͺ…: [μ΄ˆνŠΉκ°€] μ½”ν‹€λ¦° κ°•μ˜, 가격: 8000원
    }
}

πŸ“¦ let

  • runκ³Ό μœ μ‚¬ν•˜κ²Œ μ²˜λ¦¬κ°€ λλ‚˜λ©΄ μ΅œμ’…κ°’μ„ λ°˜ν™˜ν•˜μ§€λ§Œ, νŒŒλΌλ―Έν„°λ‘œ μΈμŠ€ν„΄μŠ€λ₯Ό λ„˜κΈ΄ 것과 같이 it을 톡해 μΈμŠ€ν„΄μŠ€λ₯Ό μ‚¬μš©

    • 같은 μ΄λ¦„μ˜ λ³€μˆ˜λ‚˜ ν•¨μˆ˜κ°€ μŠ€μ½”ν”„ λ°”κΉ₯에 μ€‘λ³΅λ˜μ–΄ μžˆλŠ” 경우 ν˜Όλž€μ„ λ°©μ§€ν•˜κΈ° μœ„ν•¨

fun main() {
    
    var price = 5000
    
    var a = Book("μ½”ν‹€λ¦° κ°•μ˜", 10000).apply {
        name = "[μ΄ˆνŠΉκ°€] " + name
        discount()
    }

    a.run {
        // main ν•¨μˆ˜μ˜ price λ³€μˆ˜λ₯Ό μš°μ„ 
        println("μƒν’ˆλͺ…: ${name}, 가격: ${price}원") // μƒν’ˆλͺ…: [μ΄ˆνŠΉκ°€] μ½”ν‹€λ¦° κ°•μ˜, 가격: 5000원
    }
    
    a.let {
        println("μƒν’ˆλͺ…: ${it.name}, 가격: ${it.price}원") // μƒν’ˆλͺ…: [μ΄ˆνŠΉκ°€] μ½”ν‹€λ¦° κ°•μ˜, 가격: 8000원
    }
}

Object

단 ν•˜λ‚˜μ˜ 객체만으둜 곡톡적인 속성과 ν•¨μˆ˜λ₯Ό μ‚¬μš©ν•΄μ•Ό ν•˜λŠ” 경우

μƒμ„±μž 없이 객체λ₯Ό 직접 생성

object둜 μ„ μ–Έλœ κ°μ²΄λŠ” 졜초 μ‚¬μš© μ‹œ μžλ™μœΌλ‘œ μƒμ„±λ˜κ³ , μ΄ν›„μ—λŠ” μ½”λ“œ μ „μ²΄μ—μ„œ 곡용으둜 μ‚¬μš©λ  수 μžˆλ‹€.

fun main() {
    // μΈμŠ€ν„΄μŠ€λ₯Ό μƒμ„±ν•˜μ§€ μ•Šκ³  κ·Έ 자체둜 객체
	  println(Counter.count)
    
    Counter.countUp()
    Counter.countUp()
    
    println(Counter.count)
    
    Counter.clear()
    
  	println(Counter.count)
}

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

Companion Object

  • κΈ°μ‘΄ 클래슀 μ•ˆμ— μžˆλŠ” 였브젝트(static 멀버와 μœ μ‚¬)

fun main() {
    var a = FoodPoll("짜μž₯")
    var b = FoodPoll("짬뽕")
    
    a.vote()
    a.vote()

    b.vote()
    b.vote()
    b.vote()
    
    println("${a.name} : ${a.count}")  // 짜μž₯ : 2
    println("${b.name} : ${b.count}") // 짬뽕 : 3
    println("총계 : ${FoodPoll.total}") // 총계 : 5
}

class FoodPoll (val name: String) {
    companion object {
        var total = 0 // λ‹€λ₯Έ μΈμŠ€ν„΄μŠ€μ—μ„œ κ³΅μœ ν•˜λŠ” μžμ›
    }
    
    var count = 0
    
    fun vote() {
        total++
        count++
    }
}

읡λͺ…객체와 μ˜΅μ €λ²„ νŒ¨ν„΄

μ΄λ²€νŠΈκ°€ λ°œμƒν•  λ•Œλ§ˆλ‹€ μ¦‰κ°μ μœΌλ‘œ μ²˜λ¦¬ν•  수 μžˆλ„λ‘ λ§Œλ“œλŠ” νŒ¨ν„΄

이벀트λ₯Ό μˆ˜μ‹ ν•˜λŠ” ν΄λž˜μŠ€μ™€ 이벀트의 λ°œμƒ 및 전달을 λ‹΄λ‹Ήν•˜λŠ” ν΄λž˜μŠ€μ™€ 톡신을 μœ„ν•΄ μ‚¬μš©λ˜λŠ” μΈν„°νŽ˜μ΄μŠ€λ₯Ό Observer, μ½”ν‹€λ¦°μ—μ„œλŠ” listener 라고 λΆ€λ₯Έλ‹€.

  • 이벀트λ₯Ό λ„˜κ²¨μ£ΌλŠ” ν–‰μœ„λŠ” callback

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

interface EventListener {
    fun onEvent(count: Int)
}

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

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

읡λͺ…ν΄λž˜μŠ€ ν™œμš©

  • object와 ν˜•νƒœλŠ” λΉ„μŠ·ν•˜μ§€λ§Œ 이름이 μ—†λ‹€λŠ” 차이

EventPrinter().start()

...

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

클래슀의 λ‹€ν˜•μ„±

Up-casting: μƒμœ„ μžλ£Œν˜•μΈ 수퍼클래슀λ₯Ό λ³€ν™˜

var a: Drink = Cola()

Down-casting: Up-casting된 μΈμŠ€ν„΄μŠ€λ₯Ό λ‹€μ‹œ ν•˜μœ„ μžλ£Œν˜•μœΌλ‘œ λ³€ν™˜

  • as: λ³€μˆ˜λ₯Ό ν˜Έν™˜λ˜λŠ” μžλ£Œν˜•μœΌλ‘œ λ³€ν™˜ν•΄μ£ΌλŠ” μΊμŠ€νŒ… μ—°μ‚°μž

    • λ°˜ν™˜κ°’λΏλ§Œ μ•„λ‹ˆλΌ λ³€μˆ˜ μžμ²΄λ„ λ‹€μš΄μΊμŠ€νŒ…

var a: Drink = Cola()

a as Cola // 이후 aλŠ” Cola둜 λ™μž‘
var b = a as Cola // λ³€ν™˜ κ²°κ³Όλ₯Ό λ°˜ν™˜λ°›μ•„ λ³€μˆ˜μ— μ΄ˆκΈ°ν™”
  • is: λ³€μˆ˜κ°€ μžλ£Œν˜•μ— ν˜Έν™˜λ˜λŠ”μ§€ μ²΄ν¬ν•œ ν›„ λ³€ν™˜ν•΄μ£ΌλŠ” μΊμŠ€νŒ… μ—°μ‚°μž (쑰건문 λ‚΄μ—μ„œ μ‚¬μš©)

var a: Drink = Cola()
if (a is Cola) {
    // ν•΄λ‹Ή μ˜μ—­ μ•ˆμ—μ„œλ§Œ aκ°€ Cola둜 μ‚¬μš©
}

Example

fun main() {
	var a = Drink()
    a.drink() // 음료λ₯Ό λ§ˆμ‹­λ‹ˆλ‹€.
    
    var b: Drink = Cola()
    b.drink() // μŒλ£Œμ€‘μ— 콜라λ₯Ό λ§ˆμ‹­λ‹ˆλ‹€.
    
    if (b is Cola) {
        b.washDished() // 콜라둜 μ„€κ±°μ§€λ₯Ό ν•©λ‹ˆλ‹€.
    }
    
    var c = b as Cola
    c.washDished() // 콜라둜 μ„€κ±°μ§€λ₯Ό ν•©λ‹ˆλ‹€.
    b.washDished()  // λ°˜ν™˜κ°’λΏλ§Œ μ•„λ‹ˆλΌ λ³€μˆ˜ μžμ²΄λ„ λ‹€μš΄μΊμŠ€νŒ…
}

open class Drink {
    var name = "음료"
    
    open fun drink() {
        println("${name}λ₯Ό λ§ˆμ‹­λ‹ˆλ‹€.")
    }
}

class Cola: Drink() {
    var type = "콜라"
    
    override fun drink() {
        println("${name}쀑에 ${type}λ₯Ό λ§ˆμ‹­λ‹ˆλ‹€.")
    }
    
    fun washDished() {
        println("${type}둜 μ„€κ±°μ§€λ₯Ό ν•©λ‹ˆλ‹€.")
    }
}

μ œλ„ˆλ¦­

ν΄λž˜μŠ€λ‚˜ ν•¨μˆ˜μ—μ„œ μ‚¬μš©ν•˜λŠ” μžλ£Œν˜•μ„ μ™ΈλΆ€μ—μ„œ μ§€μ •ν•  수 μžˆλŠ” κΈ°λŠ₯

ν•¨μˆ˜λ‚˜ 클래슀λ₯Ό μ„ μ–Έν•  λ•Œ 고정적인 μžλ£Œν˜• λŒ€μ‹  μ‹€μ œ μžλ£Œν˜•μœΌλ‘œ λŒ€μ²΄λ˜λŠ” νƒ€μž… νŒŒλΌλ―Έν„°λ₯Ό λ°›μ•„ μ‚¬μš©

  • μ œλ„€λ¦­μ„ μ‚¬μš©ν•  경우 μžλ£Œν˜•μ„ λŒ€μ²΄ν•˜κ²Œ λ˜μ–΄ μΊμŠ€νŒ…μ„ λ°©μ§€ν•  수 있고, μ„±λŠ₯을 높일 수 μžˆλ‹€.

ν΄λž˜μŠ€μ— 적용

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

open class A {
    open fun shout() {
        println("Aκ°€ μ†Œλ¦¬μΉ©λ‹ˆλ‹€")
    }
}

class B: A() {
    override fun shout() {
        println("Bκ°€ μ†Œλ¦¬μΉ©λ‹ˆλ‹€")
    }
}

class C: A() {
    override fun shout() {
        println("Cκ°€ μ†Œλ¦¬μΉ©λ‹ˆλ‹€")
    }
}

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

ν•¨μˆ˜μ— 적용

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

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

리슀트

μ—¬λŸ¬ 개의 데이터λ₯Ό μ›ν•˜λŠ” μˆœμ„œλ‘œ λ„£μ–΄ 관리

λ¦¬μŠ€νŠΈμ—λŠ” 두 κ°€μ§€μ˜ μ’…λ£Œκ°€ 쑴재

List<out T>

  • 생성 μ‹œ 넣은 객체λ₯Ό λŒ€μ²΄/μΆ”κ°€/μ‚­μ œ λΆˆκ°€

  • μ „μš© ν•¨μˆ˜: listOf(1, 2, 3)

MutableList<T>

  • 생성 μ‹œ 넣은 객체λ₯Ό λŒ€μ²΄/μΆ”κ°€/μ‚­μ œ κ°€λŠ₯

  • μ „μš© ν•¨μˆ˜: mutableListOf(1, 2, 3)

  • μš”μ†Œ μΆ”κ°€(add), μ‚­μ œ(remove, removeAt) κΈ°λŠ₯ 외에도

    • λ¬΄μž‘μœ„ μ„žκΈ°(shuffle), μ •λ ¬(sort) κΈ°λŠ₯도 제곡

fun main() {
  	val a = listOf("사과", "λ”ΈκΈ°", "λ°°")
    println(a[1]) // λ”ΈκΈ°
    
    for (fruit in a) {
        print("${fruit}:")
    }
    println() // 사과:λ”ΈκΈ°:λ°°:
    
    var b = mutableListOf(6, 3, 1)
    println(b) // [6, 3, 1]
    
    b.add(4)
    println(b) // [6, 3, 1, 4]
    
    b.add(2, 8) // [6, 3, 8, 1, 4]
    println(b)
    
    b.removeAt(1)
    println(b) // [6, 8, 1, 4]
    
    b.shuffle()
    println(b) // [6, 1, 4, 8]
    
    b.sort()
    println(b) // [1, 4, 6, 8]
}

λ¬Έμžμ—΄ 닀루기

λ¬Έμžμ—΄ λ³€ν˜•

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

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

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

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

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

λ¬Έμžμ—΄ 확인

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

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

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

λ¬Έμžμ—΄ 탐색

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

Null μ²˜λ¦¬μ™€ 동일성확인

Null 처리

null을 μ²˜λ¦¬ν•˜λŠ” 방법듀

?. : null safe operator

  • μ°Έμ‘°μ—°μ‚°μž μ‹€ν–‰ μ „ λ¨Όμ € 객체가 null인지 ν™•μΈν•˜κ³ , 객체 null 여뢀에 따라 뒀에 μ˜€λŠ” ꡬ문 μ‹€ν–‰ μ—¬λΆ€ νŒλ‹¨

  • sample?.toUpperCase()

?: : elvis operator

  • 객체가 null이 μ•„λ‹ˆλΌλ©΄ κ·ΈλŒ€λ‘œ μ‚¬μš©ν•˜μ§€λ§Œ, null이라면 μ—°μ‚°μž 우츑 객체둜 λŒ€μ²΄

  • sample?:”default”

!!. : non-null assertion operator

  • μ°Έμ‘°μ—°μ‚°μž μ‚¬μš© μ‹œ null μ—¬λΆ€λ₯Ό 컴파일 μ‹œ ν™•μΈν•˜μ§€ μ•Šλ„λ‘ ν•˜μ—¬ λŸ°νƒ€μž„ μ‹œ NPE이 λ°œμƒν•˜λ„λ‘ μ˜λ„μ μœΌλ‘œ 방치

  • sample!!.toUpperCase()

var a: String? = null

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

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

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

null safe μ—°μ‚°μžλŠ” μŠ€μ½”ν”„ ν•¨μˆ˜μ™€ μ‚¬μš©ν•˜λ©΄ 편리

  • null 체크λ₯Ό μœ„ν•΄ if 보닀 νŽΈλ¦¬ν•œ κΈ°λŠ₯

fun main() {
	var a: String? = null
    
	a?.run { // aκ°€ nullμ΄λ―€λ‘œ μŠ€μ½”ν”„ 전체가 λ―Έμˆ˜ν–‰
        println(toUpperCase())
        println(toLowerCase())
    }
    
    var b: String? = "Kotlin example"
    
	b?.run { // aκ°€ nullμ΄λ―€λ‘œ μŠ€μ½”ν”„ 전체가 λ―Έμˆ˜ν–‰
        println(toUpperCase())
        println(toLowerCase())
    }
}

동일성 확인

λ‚΄μš©μ˜ 동일성

  • μžλ™μœΌλ‘œ νŒλ‹¨λ˜λŠ” 것이 μ•„λ‹Œ μ½”ν‹€λ¦° λͺ¨λ“  ν΄λž˜μŠ€κ°€ λ‚΄λΆ€μ μœΌλ‘œ μƒμ†λ°›λŠ” Any μ΅œμƒμœ„ 클래슀의 equals() ν•¨μˆ˜κ°€ λ°˜ν™˜ν•˜λŠ” Boolean κ°’μœΌλ‘œ νŒλ‹¨

  • a == b

객체의 동일성

  • a === b

fun main() {
    var a = Product("콜라", 1000)
    var b = Product("콜라", 1000)
    var c = a
    var d = Product("사이닀", 1000)
    
    println(a == b) // true
    println(a === b) // false

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

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

ν•¨μˆ˜μ˜ argumentλ₯Ό λ‹€λ£¨λŠ” 방법과 infix ν•¨μˆ˜

βœ… default arguments

νŒŒλΌλ―Έν„°λ₯Ό λ°›μ•„μ•Ό ν•˜λŠ” ν•¨μˆ˜μ΄μ§€λ§Œ νŒŒλΌλ―Έν„°κ°€ 없더라도 κΈ°λ³Έκ°’μœΌλ‘œ λ™μž‘ν•΄μ•Ό ν•  경우 μ‚¬μš©

fun main() {
    deliveryItem("짬뽕") // 짬뽕, 1개λ₯Ό 집에 λ°°λ‹¬ν•˜μ˜€μŠ΅λ‹ˆλ‹€.
    deliveryItem("μ±…", 3) // μ±…, 3개λ₯Ό 집에 λ°°λ‹¬ν•˜μ˜€μŠ΅λ‹ˆλ‹€.
    deliveryItem("λ…ΈνŠΈλΆ", 30, "학ꡐ") // λ…ΈνŠΈλΆ, 30개λ₯Ό 학ꡐ에 λ°°λ‹¬ν•˜μ˜€μŠ΅λ‹ˆλ‹€.
}

fun deliveryItem(name: String, count: Int = 1, destination: String = "μ§‘") {
    println("${name}, ${count}개λ₯Ό ${destination}에 λ°°λ‹¬ν•˜μ˜€μŠ΅λ‹ˆλ‹€.")
}

단, νŒŒλΌλ―Έν„°μ˜ 쀑간을 λΉ„μš°λ©΄ λ™μž‘ν•˜μ§€ μ•ŠλŠ”λ‹€.

  • 이 κ²½μš°μ—λŠ” named arguments μ‚¬μš©

  • νŒŒλΌλ―Έν„°μ˜ μˆœμ„œμ™€ 관계없이 νŒŒλΌλ―Έν„° 이름을 μ‚¬μš©ν•˜μ—¬ 직접 νŒŒλΌλ―Έν„° 값을 ν• λ‹Ή

deliveryItem("λ…ΈνŠΈλΆ", destination = "학ꡐ")

βœ… variable number of arguments (vararg)

같은 μžλ£Œν˜•μ„ κ°œμˆ˜μ— 상관없이 νŒŒλΌλ―Έν„°λ‘œ λ°›κ³  싢을 경우 μ‚¬μš©

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

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

κ°œμˆ˜κ°€ μ§€μ •λ˜μ§€ μ•Šμ€ νŒŒλΌλ―Έν„°λΌλŠ” νŠΉμ§•μ΄ μžˆμœΌλ―€λ‘œ λ‹€λ₯Έ νŒŒλΌλ―Έν„°μ™€ 같이 μ‚¬μš©ν•  κ²½μš°μ—λŠ” 맨 λ§ˆμ§€λ§‰μ— μ„ μ–Έ

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

βœ… infix ν•¨μˆ˜

  • ν•¨μˆ˜ μ •μ˜ μ‹œ μ•žμ— infix λ₯Ό 뢙인 ν›„, ν•¨μˆ˜ 이름을 infix ν•¨μˆ˜κ°€ 적용될 μžλ£Œν˜•.μ΄λ¦„μœΌλ‘œ μ§€μ •

fun main() {
    /**
     * 6: infix ν•¨μˆ˜κ°€ μ μš©λ˜λŠ” 객체 μžμ‹ (this)
     * 4: νŒŒλΌλ―Έν„°μΈ x
     */
	println(6 multiply 4)
    // λ™μΌν•˜κ²Œ λ™μž‘
    println(6 multiply(4))
}

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

참고둜, 클래슀 μ•ˆμ—μ„œ infix ν•¨μˆ˜ μ„ μ–Έ μ‹œ 적용 ν΄λž˜μŠ€κ°€ 자기 μžμ‹ μ΄λ―€λ‘œ 클래슀 이름은 μƒλž΅ κ°€λŠ₯

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


쀑첩 ν΄λž˜μŠ€μ™€ λ‚΄λΆ€ 클래슀

Nested Class(쀑첩 클래슀)

ν˜•νƒœλ§Œ 내뢀에 μ‘΄μž¬ν•  뿐, μ™ΈλΆ€ 클래슀의 λ‚΄μš©μ„ κ³΅μœ ν•  수 μ—†λŠ” λ³„κ°œμ˜ 클래슀

Inner Class (λ‚΄λΆ€ 클래슀)

μ™ΈλΆ€ 클래슀 객체 μ•ˆμ—μ„œ μ‚¬μš©λ˜λŠ” 클래슀둜 μ™ΈλΆ€ 클래슀의 속성과 ν•¨μˆ˜ μ‚¬μš© κ°€λŠ₯

  • ν˜Όμžμ„œ 객체λ₯Ό λ§Œλ“€ μˆ˜λŠ” μ—†κ³ , μ™ΈλΆ€ 클래슀의 객체가 μžˆμ–΄μ•Όλ§Œ 생성과 μ‚¬μš©μ΄ κ°€λŠ₯

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

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

Data Class & Enum Class

βœ… Data Class

데이터λ₯Ό λ‹€λ£¨λŠ”λ° μ΅œμ ν™”λœ 클래슀

5κ°€μ§€ κΈ°λŠ₯을 λ‚΄λΆ€μ μœΌλ‘œ μžλ™ 생성

  • equals(): λ‚΄μš©μ˜ 동일성 νŒλ‹¨

  • hashcode(): 객체 λ‚΄μš©μ—μ„œ κ³ μœ ν•œ μ½”λ“œλ₯Ό 생성

  • toString(): ν¬ν•¨λœ 속성을 보기 μ‰½κ²Œ ν‘œν˜„

  • copy()

    // νŒŒλΌλ―Έν„°κ°€ μ—†λŠ” 경우 λ˜‘κ°™μ€ λ‚΄μš©μœΌλ‘œ 생성
    val a = Data("A", 7)
    val b = a.copy()
    
    // νŒŒλΌλ―Έν„°κ°€ 있으면 ν•΄λ‹Ή νŒŒλΌλ―Έν„°λ‘œ κ΅μ²΄ν•˜μ—¬ 생성
    val a = Data("A", 7)
    val b = a.copy("B")

Example

fun main() {
  	val a = General("보영", 212)
    
    println(a == General("보영", 212)) // false
    println(a.hashCode()) // 20132171
    println(a) // General@133314b
    
    val b = Data("루닀", 306)

    println(b == Data("루닀", 306)) // true
    println(b.hashCode()) // 46909878
    println(b) // Data(name=루닀, id=306)
    
    println(b.copy()) // Data(name=루닀, id=306)
    println(b.copy("μ•„λ¦°")) // Data(name=μ•„λ¦°, id=306)
    println(b.copy(id = 618)) // Data(name=루닀, id=618)
}

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

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

βœ… componentX(): 속성을 μˆœμ„œλŒ€λ‘œ λ°˜ν™˜

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

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

Example

fun main() {
	val list = listOf(Data("보영", 212),
                     Data("루닀", 306),
                     Data("μ•„λ¦°", 618))
    
    for ((a, b) in list) {
        // λ‚΄λΆ€μ μœΌλ‘œ component1(), component2() ν•¨μˆ˜ μ‚¬μš©
        println("${a}, ${b}")
    }
}

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

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

βœ… Enum Class

enumerated type (μ—΄κ±°ν˜•)

enum 클래슀 μ•ˆμ˜ 객체듀은 κ΄€ν–‰μ μœΌλ‘œ μƒμˆ˜λ₯Ό λ‚˜νƒ€λ‚Ό λ•Œ μ‚¬μš©ν•˜λŠ” λŒ€λ¬Έμžλ‘œ 기술

  • enum의 객체듀은 κ³ μœ ν•œ 속성을 κ°€μ§ˆ 수 있음

fun main() {
	var state = State.SING
    println(state) // SING (toString을 톡해 μƒνƒœ 객체의 이름이 좜λ ₯)
    
    state = State.SLEEP
    println(state.isSleeping()) // true
    
    state = State.EAT
    println(state.message) // λ°₯을 λ¨ΉμŠ΅λ‹ˆλ‹€
}

enum class State(val message: String) {
    SING("λ…Έλž˜λ₯Ό λΆ€λ¦…λ‹ˆλ‹€"),
    EAT("λ°₯을 λ¨ΉμŠ΅λ‹ˆλ‹€"),
    SLEEP("μž μ„ μž‘λ‹ˆλ‹€");
    
   fun isSleeping() = this == State.SLEEP
}

Set & Map

βœ… Set

μˆœμ„œκ°€ μ •λ ¬λ˜μ§€ μ•ŠμœΌλ©°, 쀑볡이 ν—ˆμš©λ˜μ§€ μ•ŠλŠ” μ»¬λ ‰μ…˜

fun main() {
	  val a = mutableSetOf("κ·€", "λ°”λ‚˜λ‚˜", "ν‚€μœ„")
    
    for (item in a) {
        println("${item}") // κ·€ λ°”λ‚˜λ‚˜ ν‚€μœ„
    }
    
    a.add("자λͺ½")
    println(a) // [κ·€, λ°”λ‚˜λ‚˜, ν‚€μœ„, 자λͺ½]
    
    a.remove("λ°”λ‚˜λ‚˜")
    println(a) // [κ·€, ν‚€μœ„, 자λͺ½]
    
    println(a.contains("κ·€")) // true
}

βœ… Map

객체λ₯Ό 넣을 λ•Œ κ·Έ 객체λ₯Ό μ°Ύμ•„λ‚Ό 수 μžˆλŠ” Keyλ₯Ό 쌍으둜 λ„£μ–΄μ£ΌλŠ” μ»¬λ ‰μ…˜

fun main() {
	val a = mutableMapOf("λ ˆλ“œλ²¨λ²³" to "음파음파",
                         "νŠΈμ™€μ΄μŠ€" to "FANCY",
                         "ITZY" to "ICY")
    
    for (entry in a) {
        println("${entry.key} : ${entry.value}") // λ ˆλ“œλ²¨λ²³ : 음파음파, νŠΈμ™€μ΄μŠ€ : FANCY, ITZY : ICY
    }
    
    a.put("였마이걸", "λ²ˆμ§€")
    println(a) // {λ ˆλ“œλ²¨λ²³=음파음파, νŠΈμ™€μ΄μŠ€=FANCY, ITZY=ICY, 였마이걸=λ²ˆμ§€}
    
    a.remove("ITZY")
    println(a) // {λ ˆλ“œλ²¨λ²³=음파음파, νŠΈμ™€μ΄μŠ€=FANCY, 였마이걸=λ²ˆμ§€}
    
    println(a["λ ˆλ“œλ²¨λ²³"]) // 음파음파
}

μ»¬λ ‰μ…˜ ν•¨μˆ˜

forEach

  • μ»¬λ ‰μ…˜ μ•ˆμ—μ„œ λͺ¨λ“  μ›μ†Œλ₯Ό it 을 톡해 μ°Έμ‘°

  • collection.forEach { println(it) }

filter

  • μ»¬λ ‰μ…˜ μ•ˆμ—μ„œ 쑰건에 λ§žλŠ” μ›μ†Œλ₯Ό λͺ¨μ•„μ„œ λ‹€μ‹œ μ»¬λ ‰μ…˜μœΌλ‘œ λ°˜ν™˜

  • collection.filter { it < 4 }

map

  • μˆ˜μ‹μ„ 톡해 μ—°μ‚°λœ κ²°κ³Όλ₯Ό μ»¬λ ‰μ…˜μœΌλ‘œ λ°˜ν™˜

  • collection.map { it * 2 }

any

  • ν•˜λ‚˜λΌλ„ 쑰건에 맞으면 true

  • collection.any { it == 0 }

all

  • λͺ¨λ‘ 쑰건에 맞으면 true

  • collection.all { it == 0 }

none

  • ν•˜λ‚˜λΌλ„ 쑰건에 λ§žμ§€ μ•ŠμœΌλ©΄ true

  • collection.none { it == 0 }

first

  • collection.first(): μ»¬λ ‰μ…˜μ˜ 첫 번째 μ•„μ΄ν…œ λ°˜ν™˜

  • collection.first{ it > 3 } : 쑰건에 λ§žλŠ” 첫번째 μ•„μ΄ν…œ λ°˜ν™˜

  • find ν•¨μˆ˜λ‘œ λŒ€μ²΄ κ°€λŠ₯

last

  • collection.last{ it > 3 } : 쑰건에 λ§žλŠ” λ§ˆμ§€λ§‰ μ•„μ΄ν…œ λ°˜ν™˜

  • findLast ν•¨μˆ˜λ‘œ λŒ€μ²΄ κ°€λŠ₯

⚠️ first, last ν•¨μˆ˜λŠ” 쑰건에 λ§žλŠ” 객체가 μ—†λŠ” 경우 NoSuchElementException λ°œμƒ

  • 이 경우 firstOrNull, lastOrNull ν™œμš©

count

  • collection.count() : μ»¬λ ‰μ…˜μ˜ λͺ¨λ“  μ•„μ΄ν…œ 개수 λ°˜ν™˜

  • collection.count { it > 7 } : 쑰건에 λ§žλŠ” μ•„μ΄ν…œ 개수 λ°˜ν™˜

fun main() {
	val nameList = listOf("λ°•μˆ˜μ˜", "κΉ€μ§€μˆ˜", "κΉ€λ‹€ν˜„", "μ‹ μœ λ‚˜", "κΉ€μ§€μš°")
    
    nameList.forEach { print(it + " ") } // λ°•μˆ˜μ˜ κΉ€μ§€μˆ˜ κΉ€λ‹€ν˜„ μ‹ μœ λ‚˜ κΉ€μ§€μš° 
    println()
    
    println(nameList.filter { it.startsWith("κΉ€") }) // [κΉ€μ§€μˆ˜, κΉ€λ‹€ν˜„, κΉ€μ§€μš°]
    println(nameList.map { "이름 : " + it }) // [이름 : λ°•μˆ˜μ˜, 이름 : κΉ€μ§€μˆ˜, 이름 : κΉ€λ‹€ν˜„, 이름 : μ‹ μœ λ‚˜, 이름 : κΉ€μ§€μš°]

    println(nameList.any { it == "κΉ€μ§€μ—°" } ) // false
    println(nameList.all { it.length == 3 } ) // true
    println(nameList.none { it.startsWith("이") }) // true
    
    println(nameList.first { it.startsWith("κΉ€") }) // κΉ€μ§€μˆ˜
    println(nameList.last { it.startsWith("κΉ€") }) // κΉ€μ§€μš°
    println(nameList.count { it.contains("μ§€") }) // 2
}

associateBy

  • list의 μ•„μ΄ν…œμ—μ„œ keyλ₯Ό μΆ”μΆœν•˜μ—¬ map 으둜 λ³€ν™˜ν•˜λŠ” ν•¨μˆ˜

  • collection.associateBy { it.name }

groupBy

  • keyκ°€ 같은 μ•„μ΄ν…œλΌλ¦¬ λ°°μ—΄λ‘œ λ¬Άμ–΄ map으둜 λ§Œλ“œλŠ” ν•¨μˆ˜

  • collection.groupBy { it.birthYear }

partition

  • μ•„μ΄ν…œμ— 쑰건을 κ±Έμ–΄ 두 개의 μ»¬λ ‰μ…˜μœΌλ‘œ λ‚˜λˆ„λŠ” ν•¨μˆ˜

  • collection.partition { it.birthYear > 2002 }

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

fun main() {
  	data class Person(val name: String, val birthYear: Int)
    
    val personList = listOf(Person("μœ λ‚˜", 1992),
                            Person("쑰이", 1996),
                            Person("μΈ„", 1999),
                            Person("μœ λ‚˜", 2003))
    
    // {1992=Person(name=μœ λ‚˜, birthYear=1992), 1996=Person(name=쑰이, birthYear=1996), 1999=Person(name=μΈ„, birthYear=1999), 2003=Person(name=μœ λ‚˜, birthYear=2003)}
    println(personList.associateBy{ it.birthYear })
    // {μœ λ‚˜=[Person(name=μœ λ‚˜, birthYear=1992), Person(name=μœ λ‚˜, birthYear=2003)], 쑰이=[Person(name=쑰이, birthYear=1996)], μΈ„=[Person(name=μΈ„, birthYear=1999)]}
    println(personList.groupBy{ it.name })
    
    val (over98, under98) = personList.partition { it.birthYear > 1998 }
    println(over98) // [Person(name=μΈ„, birthYear=1999), Person(name=μœ λ‚˜, birthYear=2003)]
    println(under98) // [Person(name=μœ λ‚˜, birthYear=1992), Person(name=쑰이, birthYear=1996)]
}

flatMap

  • μ•„μ΄ν…œλ§ˆλ‹€ λ§Œλ“€μ–΄μ§„ μ»¬λ ‰μ…˜μ„ ν•©μ³μ„œ λ°˜ν™˜ν•˜λŠ”

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

getOrElse

  • 인덱슀 μœ„μΉ˜μ— μ•„μ΄ν…œμ΄ 있으면 μ•„μ΄ν…œμ„ λ°˜ν™˜ν•˜κ³ , μ•„λ‹Œ 경우 μ§€μ •ν•œ 기본값을 λ°˜ν™˜

  • collection.getOrElse(1) { 50 }

zip

  • μ»¬λ ‰μ…˜ 두 개의 μ•„μ΄ν…œμ„ 1:1둜 λ§€μΉ­ν•˜μ—¬ μƒˆ μ»¬λ ‰μ…˜μœΌλ‘œ 생성

  • κ²°κ³Ό 리슀트의 μ•„μ΄ν…œ κ°œμˆ˜λŠ” 더 μž‘μ€ μ»¬λ ‰μ…˜μ„ 따라감

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

λ³€μˆ˜μ˜ κ³ κΈ‰ 기술

βœ… μƒμˆ˜

컴파일 μ‹œμ μ— κ²°μ •λ˜μ–΄ λ°”κΏ€ 수 μ—†λŠ” κ°’

const val CONST_A = 1234

μƒμˆ˜λ‘œ 선언될 수 μžˆλŠ” 값은 κΈ°λ³Έ μžλ£Œν˜•λ§Œ κ°€λŠ₯

  • λŸ°νƒ€μž„μ— 생성될 수 μžˆλŠ” 일반적인 λ‹€λ₯Έ 클래슀의 객체듀은 담을 수 μ—†λ‹€.

  • 클래슀의 μ†μ„±μ΄λ‚˜ μ§€μ—­λ³€μˆ˜λ‘œλŠ” μ‚¬μš© λΆˆκ°€

λ°˜λ“œμ‹œ companion object μ•ˆμ— μ„ μ–Έν•˜μ—¬ 객체의 생성과 관계없이 ν΄λž˜μŠ€μ™€ κ΄€κ³„λœ 고정적인 κ°’μœΌλ‘œλ§Œ μ‚¬μš© κ°€λŠ₯

  • λ³€μˆ˜μ˜ 경우 λŸ°νƒ€μž„ μ‹œ 객체 생성에 μ‹œκ°„μ΄ 더 μ†Œμš”λ˜μ–΄ μ„±λŠ₯ ν•˜λ½μ΄ μžˆμ–΄ 이λ₯Ό λ§‰κ³ μž μƒμˆ˜λ₯Ό μ‚¬μš©

Example

fun main() {
	val foodCourt = FoodCourt()
    
    foodCourt.searchPrice(FoodCourt.FOOD_CREAM_PASTA) // ν¬λ¦ΌνŒŒμŠ€νƒ€μ˜ 가격은 13000원 μž…λ‹ˆλ‹€.
    foodCourt.searchPrice(FoodCourt.FOOD_STEAK) // μŠ€ν…Œμ΄ν¬μ˜ 가격은 25000원 μž…λ‹ˆλ‹€.
    foodCourt.searchPrice(FoodCourt.FOOD_PIZZA) // ν”Όμžμ˜ 가격은 15000원 μž…λ‹ˆλ‹€.
}

class FoodCourt {
    fun searchPrice(foodName: String) {
        val price = when(foodName) {
            FOOD_CREAM_PASTA -> 13000
            FOOD_STEAK -> 25000
            FOOD_PIZZA -> 15000
            else -> 0
        }
        
        println("${foodName}의 가격은 ${price}원 μž…λ‹ˆλ‹€.")
    }
    
    companion object {
        const val FOOD_CREAM_PASTA = "ν¬λ¦ΌνŒŒμŠ€νƒ€"
        const val FOOD_STEAK = "μŠ€ν…Œμ΄ν¬"
        const val FOOD_PIZZA = "ν”Όμž"
    }
}

βœ… lateinit

일반 λ³€μˆ˜λ§Œ μ„ μ–Έν•˜κ³  μ΄ˆκΈ°κ°’μ˜ 할당은 λ‚˜μ€‘μ— ν•  수 μžˆλ„λ‘ ν•˜λŠ” ν‚€μ›Œλ“œ

  • μ΄ˆκΈ°κ°’ ν• λ‹Ή μ „κΉŒμ§€ λ³€μˆ˜λ₯Ό μ‚¬μš©ν•  수 μ—†μŒ(μ—λŸ¬ λ°œμƒ)

  • κΈ°λ³Έ μžλ£Œν˜•μ—λŠ” μ‚¬μš© λΆˆκ°€

lateinit var a: Int

lateinit λ³€μˆ˜μ˜ μ΄ˆκΈ°ν™” μ—¬λΆ€ 확인

::a.isInitialized

Example

fun main() {
	val a = LateInitSample()
    
    println(a.getLateInitText()) // κΈ°λ³Έκ°’
    a.text = "μƒˆλ‘œ ν• λ‹Ήν•œ κ°’"
    println(a.getLateInitText()) // μƒˆλ‘œ ν• λ‹Ήν•œ κ°’
}

class LateInitSample {
    lateinit var text: String
    
    fun getLateInitText(): String {
        if (::text.isInitialized) {
            return text
        }
        return "κΈ°λ³Έκ°’"
    }
}

βœ… lazy delegate properties

λ³€μˆ˜λ₯Ό μ‚¬μš©ν•˜λŠ” μ‹œμ κΉŒμ§€ μ΄ˆκΈ°ν™”λ₯Ό μžλ™μœΌλ‘œ λŠ¦μΆ°μ£ΌλŠ” μ§€μ—° λŒ€λ¦¬μž 속성

  • μ½”λ“œμƒμœΌλ‘œλŠ” μ¦‰μ‹œ 객체λ₯Ό 생성 및 ν• λ‹Ήν•˜μ—¬ λ³€μˆ˜λ₯Ό μ΄ˆκΈ°ν™”ν•˜λŠ” ν˜•νƒœλ₯Ό κ°–μ§€λ§Œ

  • μ‹€μ œ μ‹€ν–‰μ‹œμ—λŠ” val λ³€μˆ˜ μ‚¬μš© μ‹œμ μ— μ΄ˆκΈ°ν™”

val a: Int by lazy { 7 }
...
println(a) // 이 μ‹œμ μ— 7둜 μ΄ˆκΈ°ν™”

Example

fun main() {
	val number: Int by lazy {
        println("μ΄ˆκΈ°ν™” μ§„ν–‰")
        7
    }	
    
    println("μ½”λ“œ μ‹œμž‘")
    println(number)
    println(number)
    /**
     * μ½”λ“œ μ‹œμž‘
     * μ΄ˆκΈ°ν™” μ§„ν–‰
     * 7
     * 7
     */
}

코루틴을 ν†΅ν•œ 비동기 처리

βœ… μ½”λ£¨ν‹΄μ˜ Scope

코루틴은 μ œμ–΄λ²”μœ„ 및 μ‹€ν–‰λ²”μœ„ μ§€μ • κ°€λŠ₯

  • GlobalScope

    • ν”„λ‘œκ·Έλž¨ μ–΄λ””μ„œλ‚˜ μ œμ–΄, λ™μž‘μ΄ κ°€λŠ₯ν•œ κΈ°λ³Έ λ²”μœ„

  • CoroutineScope

    • νŠΉμ •ν•œ λͺ©μ μ˜ Dispatcherλ₯Ό μ§€μ •ν•˜μ—¬ μ œμ–΄ 및 λ™μž‘μ΄ κ°€λŠ₯ν•œ λ²”μœ„

βœ… CoroutineScope Dispatcher

  • Dispatchers.Default: 기본적인 λ°±κ·ΈλΌμš΄λ“œ λ™μž‘

  • Dispatchers.IO: I/O에 μ΅œμ ν™” 된 λ™μž‘

  • Dispatchers.Main : 메인(UI) μŠ€λ ˆλ“œμ—μ„œ λ™μž‘

λͺ¨λ“  ν”Œλž«νΌμ—μ„œ μ§€μ›λ˜μ§€λŠ” μ•ŠμœΌλ‹ˆ μ§€μ›λ˜λŠ” ν”Œλž«νΌμ— 따라 μ‚¬μš©

코루틴은 μ΄λŸ¬ν•œ Scopeμ—μ„œ μ œμ–΄λ˜λ„λ‘ 생성될 수 있음

// μƒμ„±λœ μŠ€μ½”ν”„μ—μ„œ
val scope = CoroutineScope(Dispatcher.Defaunt)
// μƒˆλ‘œμš΄ 코루틴 생성
val coroutineA = scope.launch {}
val coroutineB = scope.async {}
  • launch vs. async : λ°˜ν™˜κ°’μ΄ μžˆλŠ”μ§€μ˜ μ—¬λΆ€

    • launch: λ°˜ν™˜κ°’μ΄ μ—†λŠ” Job 객체

      import kotlinx.coroutines.*
      
      fun main() {
      	
          val scope = GlobalScope
      
          runBlocking { // 코루틴이 μ’…λ£Œλ  λ•ŒκΉŒμ§€ 메인 루틴을 μž μ‹œ λŒ€κΈ°
              // Job 객체의 코루틴 생성
              scope.launch {
                  for (i in 1..5) {
                      println(i)
                  }
              }
              
              // launchλ₯Ό 직접 생성
              launch {
                  for (i in 6..10) {
                      println(i)
                  }
              }
      	}
      }
    • async: λ°˜ν™˜κ°’μ΄ μžˆλŠ” Deffered 객체

      async {
          var sum = 0
          for (i in 1..10) {
              sum++
          }
          sum // 이 값이 λ°˜ν™˜
      }

βœ… λ£¨ν‹΄μ˜ λŒ€κΈ°λ₯Ό μœ„ν•œ 좔가적인 ν•¨μˆ˜λ“€

코루틴 λ‚΄λΆ€ λ˜λŠ” runBlocking 같은 λ£¨ν‹΄μ˜ λŒ€κΈ°κ°€ κ°„μœΌν•œ ꡬ문 μ•ˆμ—μ„œλ§Œ λ™μž‘ κ°€λŠ₯

  • delay(milisecond: Long): ms λ‹¨μœ„λ‘œ 루틴을 μž μ‹œ λŒ€κΈ°μ‹œν‚€λŠ” ν•¨μˆ˜

  • Job.join(): Job의 싀행이 λλ‚ λ•ŒκΉŒμ§€ λŒ€κΈ°ν•˜λŠ” ν•¨μˆ˜

  • Deferred.await(): Deferred의 싀행이 λλ‚ λ•ŒκΉŒμ§€ λŒ€κΈ°ν•˜λŠ” ν•¨μˆ˜

    • Deferred 결과도 λ°˜ν™˜

import kotlinx.coroutines.*

fun main() {
    
    runBlocking {
        val a = launch {
            for (i in 1..5) {
                println(i)
                delay(10)
            }
        }
        
        val b = async {
            "async μ’…λ£Œ"
        }
        
        println("async λŒ€κΈ°")
        println(b.await()) // Deferred의 싀행이 λλ‚ λ•ŒκΉŒμ§€ λŒ€κΈ°
        
				println("launch λŒ€κΈ°")
        a.join() // Job의 싀행이 λλ‚ λ•ŒκΉŒμ§€ λŒ€κΈ°
        println("launch μ’…λ£Œ")
	}
    /**
     * async λŒ€κΈ°
     * 1
     * async μ’…λ£Œ
     * launch λŒ€κΈ°
     * 2
     * 3
     * 4
     * 5
     * launch μ’…λ£Œ
     */
}

βœ… 코루틴 μ‹€ν–‰ 도쀑 μ€‘λ‹¨ν•˜κΈ°

코루틴에 cancel()을 κ±Έμ–΄μ£Όλ©΄ λ‹€μŒ 두 κ°€μ§€ 쑰건이 λ°œμƒν•˜λ©° 코루틴을 쀑단 κ°€λŠ₯

  • 코루틴 λ‚΄λΆ€μ˜ delay() ν•¨μˆ˜ λ˜λŠ” yield() ν•¨μˆ˜κ°€ μ‚¬μš©λœ μœ„μΉ˜κΉŒμ§€ μˆ˜ν–‰λœ λ’€ μ’…λ£Œ

  • cancel()둜 인해 속성인 isActiveκ°€ false λ˜λ―€λ‘œ 이λ₯Ό ν™•μΈν•˜μ—¬ μˆ˜λ™μœΌλ‘œ μ’…λ£Œ

import kotlinx.coroutines.*

fun main() {
    
    runBlocking {
        val a = launch {
            for (i in 1..5) {
                println(i)
                delay(10)
            }
        }
        
        val b = async {
            "async μ’…λ£Œ"
        }
        
        println("async λŒ€κΈ°")
        println(b.await())
        
		println("launch μ·¨μ†Œ")
        a.cancel()
        println("launch μ’…λ£Œ")
	}
    /**
     * async λŒ€κΈ°
     * 1
     * async μ’…λ£Œ
     * launch μ·¨μ†Œ
     * launch μ’…λ£Œ
     */
}

βœ… μ œν•œμ‹œκ°„ 내에 μˆ˜ν–‰λ˜λ©΄ 결과값을 μ•„λ‹Œ 경우 null λ°˜ν™˜ν•˜λŠ” withTimeoutOrNull()

import kotlinx.coroutines.*

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

Kotlin Steps

JPA Entity μ½”ν‹€λ¦°μŠ€λŸ½κ²Œ μ‚¬μš©ν•˜κΈ°

πŸ€·πŸ»β€β™‚οΈ Data Calss ν™œμš©?

Entity의 동등성 μ²΄ν¬λŠ” λͺ¨λ“  ν”„λ‘œνΌν‹°λ₯Ό λΉ„κ΅ν•˜λŠ”κ²Œ μ•„λ‹ˆλΌ μ‹λ³„μžλ₯Ό ν†΅ν•΄μ„œλ§Œ 비ꡐ

  • equals와 hashCodeλ₯Ό λ”°λ‘œ μž¬μ •μ˜λ₯Ό ν•˜μ§€ μ•ŠμœΌλ©΄ μ°Έμ‘° 비ꡐλ₯Ό 톡해 동일성 확인을 ν•˜λ―€λ‘œ

    • μ‹λ³„μžλ₯Ό ν†΅ν•œ 동등성 νŒλ‹¨μ„ μ œκ³΅ν•˜λ €λ©΄ equals와 hashCode μž¬μ •μ˜ ν•„μš”

πŸ€·πŸ»β€β™‚οΈ lateinit 을 μ‚¬μš©ν•œ μ΄ˆκΈ°ν™” 미루기?

μ΄ˆκΈ°ν™”λ₯Ό μ΅œλŒ€ν•œ λ’€λ‘œ λŠ¦μΆ°μ„œ μ„±λŠ₯ 및 νš¨μœ¨μ„±μ„ λ†’νžˆλ €λŠ” μš©λ„λ‘œ μ‚¬μš©

  • 일반적으둜 연관관계 없이 Column만 μ‘΄μž¬ν•˜λŠ” 경우 lateinitλ₯Ό μ‚¬μš©ν•˜μ§€ μ•ŠμŒ

  • ν•˜μ§€λ§Œ, 연관관계λ₯Ό μ •μ˜ν•˜λŠ” 경우 lateinit μ •μ˜κ°€ ν•„μš”ν•œλ°

    • μ˜μ†ν™”λœ μ—”ν‹°ν‹°λ₯Ό μ‘°νšŒν•  λ•ŒλŠ” JPAκ°€ lateinit ν•„λ“œλ₯Ό μ΄ˆκΈ°ν™”ν•΄ μ£Όμ§€λ§Œ

    • 이제 막 μƒμ„±ν•œ EntityλŠ” JPAκ°€ lateinit ν•„λ“œλ₯Ό μ΄ˆκΈ°ν™” ν•΄μ£Όμ§€ μ•Šμ•˜μœΌλ―€λ‘œ μ—”ν‹°ν‹° κ·Έλž˜ν”„ 탐색 μ‹œμ μ— 였λ₯˜κ°€ λ°œμƒ

πŸ™†πŸ»β€β™‚οΈ lateinit을 μ‚¬μš©ν•˜μ§€ μ•Šκ³  Java처럼 연관관계λ₯Ό μ •μ˜ν•˜λ €λ©΄?

μ—”ν‹°ν‹° 자체λ₯Ό λ„£μ–΄μ£ΌκΈ°

@Entity
class Board(
  title: String,
  writer: User, // 생성 μ‹œμ μ— μƒμ„±μž νŒŒλΌλ―Έν„°λ‘œ μž‘μ„±μžλ₯Ό λ°›μ•„μ„œ μ΄ˆκΈ°ν™”
) {
  @Id
  var id: UUID = UUID.randomUUID()

  @Column
  var title: String = title

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

Example

Entity

PrimaryKeyEntity.kt

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

    @Transient
    private var _isNew = true

    override fun getId(): UUID = id
        
        /**
            * Persistable μΈν„°νŽ˜μ΄μŠ€λ₯Ό κ΅¬ν˜„ν•œ Entityλ₯Ό μ˜μ†ν™” ν•˜λ €ν•˜λ©΄, 
            * JpaPersistableEntityInformation.isNew ν•¨μˆ˜κ°€ 호좜되며 
            * λ‚΄λΆ€μ μœΌλ‘œ Persistable.isNew ν•¨μˆ˜λ₯Ό 호좜
            */
    override fun isNew(): Boolean = _isNew
        
        // PrimaryKeyEntity 상속받은 엔티티듀이 κ³΅ν†΅μœΌλ‘œ μ‚¬μš©ν•  수 μžˆλŠ” 
        // equals, hashCode μž¬μ •μ˜
    override fun equals(other: Any?): Boolean {
        if (other == null) {
            return false
        }
                
                // μ§€μ—° 쑰회둜 인해 Entityκ°€ μ‹€ν–‰λ˜κΈ° μ „κΉŒμ§€λŠ” Proxy 객체λ₯Ό 미리 μƒμ„±ν•˜λ―€λ‘œ 
                // ν”„λ‘μ‹œ νƒ€μž…κΉŒμ§€ 같이 κ³ λ €
        if (other !is HibernateProxy && 
                        this::class != other::class) {
            return false
        }

        return id == getIdentifier(other)
    } 

    private fun getIdentifier(obj: Any): Serializable {
        return if (obj is HibernateProxy) {
            // ν”„λ‘μ‹œ 객체일 경우 μ‹λ³„μž 정보가 μ‘΄μž¬ν•˜λŠ” κ³³μ—μ„œ μ‹λ³„μžλ₯Ό κ°€μ Έμ˜΄
            obj.hibernateLazyInitializer.identifier
        } else {
            (obj as PrimaryKeyEntity).id
        }
    }

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

    /**
        * JPA delete λ©”μ„œλ“œμ—μ„œλŠ” μƒˆλ‘œμš΄ 엔티티라면 return μ²˜λ¦¬ν•˜λ‹€ λ³΄λ‹ˆ
        * μ—”ν‹°ν‹°κ°€ μ˜μ†ν™” μ΄ν›„μ—λŠ” isNewκ°€ falseλ₯Ό λ°˜ν™˜ν•˜λ„λ‘ μ„€μ •
        */
    @PostPersist // μ˜μ†ν™” 이후 μ‹€ν–‰ μ• λ…Έν…Œμ΄μ…˜
    @PostLoad // μ˜μ†ν™”ν•œ 데이터 쑰회 이후 μ‹€ν–‰ μ• λ…Έν…Œμ΄μ…˜
    protected fun load() {
        _isNew = false
    }
}

User.kt

@Entity
@Table(name = "`user`")
class User(
    name: String,
) : PrimaryKeyEntity() {
    @Column(nullable = false, unique = true)
    var name: String = name
        protected set // User Entity μžμ‹ μ΄λ‚˜ 상속 Entityμ—μ„œλ§Œ 이름 λ³€κ²½ κ°€λŠ₯

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

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

Tag.kt

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

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

Board.kt

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

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

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

    @Embedded
    var information: BoardInformation = information
        protected set

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

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

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

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

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

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

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

    init {
        writer.writeBoard(this)
    }
}

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

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

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

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

(1) allopen

μ½”ν‹€λ¦°μ—μ„œλŠ” JPA Entity μ„€κ³„μ‹œ allopen, no-args constructor μ˜΅μ…˜ ν•„μš”

JPAμ—μ„œλŠ” 클래슀 ν™•μž₯ 및 ν”„λ‘μ‹œλ₯Ό λ§Œλ“€κΈ° μœ„ν•΄ 클래슀λ₯Ό μƒμ†ν•˜λ € ν•˜λŠ”λ° ν΄λž˜μŠ€κ°€ final class라면 ν™•μž₯이 λΆˆκ°€λŠ₯ν•˜κΈ° λ•Œλ¬Έμ— λ¬Έμ œκ°€ λ°œμƒ

  • JPA ν”ŒλŸ¬κ·ΈμΈμ€ JPA κ΄€λ ¨ 클래슀 생성에 λ¬Έμ œκ°€ 없도둝 μƒμ„±μž λ§€κ°œλ³€μˆ˜κ°€ μ—†λŠ” No-arg plugin도 ν¬ν•¨λ˜λŠ”λ°, μ΄λŠ” JPAμ—μ„œ μ—”ν‹°ν‹° λ§€ν•‘ 방식이 λ¦¬ν”Œλ ‰μ…˜μ„ μ΄μš©ν•œ ν”„λ‘œνΌν‹° μ£Όμž…μ΄κΈ° λ•Œλ¬Έ

  • κ·Έλž˜μ„œ 보톡 μ½”ν‹€λ¦°μœΌλ‘œ JPA ν”„λ‘œμ νŠΈλ₯Ό μ„€μ •ν•  λ•Œ, ν”ŒλŸ¬κ·ΈμΈμ„ μΆ”κ°€ν•΄μ„œ ν΄λž˜μŠ€λ“€μ΄ μžλ™μœΌλ‘œ open될 수 μžˆλ„λ‘ μ„€μ • ν•„μš”

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

⚠️ ν•˜μ§€λ§Œ, ν”ŒλŸ¬κ·ΈμΈμ„ μΆ”κ°€ν•˜λ”λΌλ„ Entity Decompile을 해보면 final ν‚€μ›Œλ“œκ°€ μžˆλ‹€.

  • κ·Έλž˜μ„œ Hibernate μ‚¬μš©μ„ μœ„ν•΄ Entity, Entity의 μΈμŠ€ν„΄μŠ€ λ³€μˆ˜λŠ” final이 μ•„λ‹ˆμ–΄μ•Ό ν•˜λ―€λ‘œ μΆ”κ°€ 섀정이 ν•„μš”

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

(2) PrimaryKeyEntity

βœ… μ—”ν‹°ν‹° 곡톡 μ‹λ³„μž

λͺ¨λ“  Entityκ°€ PrimaryKeyEntityλ₯Ό 상속받아 μ‚¬μš©ν•˜λ„λ‘ ν•˜λ©΄ κ³΅ν†΅λœ PrimaryKey λ₯Ό μ‚¬μš©

  • Integer, Long νƒ€μž…μ€ Entity 간에도 ν‚€ 값이 쀑볡될 수 있고 MAX_VALUE의 차이도 μžˆμ–΄μ„œ UUIDλ₯Ό 많이 μ„ νƒν•˜λŠ” μΆ”μ„Έ

  • ν•˜μ§€λ§Œ, UUID λ˜ν•œ μ •λ ¬λΆˆκ°€λŠ₯ν•˜λ‹€λŠ” 단점과 Long보닀 ν¬κΈ°λ‚˜ μƒμ„±λΉ„μš©λ„ ν¬λ‹€λŠ” 단점 쑴재

    • 정렬은 ULIDλ₯Ό μ‚¬μš©ν•΄μ„œ ν•΄κ²°ν•  수 있고

    • ν¬κΈ°λŠ” κ³ λ €ν•  μ •λ„μ˜ μ‹œμŠ€ν…œμ΄λΌλ©΄ ORM μ‚¬μš© 자체λ₯Ό κ³ λ €ν•΄ 보아야 ν•  것이닀.

ULIDλŠ” UUID와 ν˜Έν™˜μ„±μ„ κ°€μ§€λ©° μ‹œκ°„μˆœμœΌλ‘œ μ •λ ¬ν•  수 μžˆλ‹€λŠ” νŠΉμ§•

    • Monotinic ν•¨μˆ˜λ₯Ό μ œκ³΅ν•΄μ€˜μ„œ κΈ°μ‘΄ ULIDκ°€ λ°€λ¦¬μ΄ˆκΉŒμ§€λ§Œ μ œκ³΅λ˜λŠ”λ°, λ™μΌν•œ λ°€λ¦¬μ΄ˆκ°€ μžˆμ„ 경우 λ‹€μŒ 생성 ULID의 λ°€λ¦¬μ΄ˆλ₯Ό 1μ¦κ°€μ‹œμΌœ 생성

βœ… Nullable νƒ€μž…μ„ λ°©μ§€

κΈ°μ‘΄ μžλ°”μ—μ„œλŠ” JPA μ—”ν‹°ν‹°μ˜ New μ—¬λΆ€λ₯Ό μ‹λ³„μž(null or 0)둜 νŒλ‹¨ν•˜μ§€λ§Œ

PrimaryKeyEntityλŠ” Persistable μΈν„°νŽ˜μ΄μŠ€λ₯Ό κ΅¬ν˜„ν•˜μ—¬ Persistable.isNew ν•¨μˆ˜ ν™œμš©

βœ… 곡톡 동일성 보μž₯

EntityλŠ” 같은 μ‹λ³„μžλ₯Ό κ°€μ§ˆ 경우 λ™μΌν•œ 객체둜 νŒλ‹¨ν•΄μ•Ό ν•˜κΈ°λ―€λ‘œ equals, hashCode μž¬μ •μ˜κ°€ ν•„μš”ν•œλ°

PrimaryKeyEntity μ—μ„œ equals, hashCodeλ₯Ό μž¬μ •μ˜ ν•œ λ’€ κ³΅ν†΅μœΌλ‘œ μ‚¬μš©ν•œλ‹€λ©΄ 이런 λΆˆνŽΈν•¨μ„ 없앨 수 μžˆλ‹€.

(3) μ±„λ²ˆ μœ λ°œμ„ λ°©μ§€

κΈ°μ‘΄ JPAμ—μ„œμ˜ ID 생성 방법은 @GeneratedValue의 auto_increment, sequence, sequence table λ“±μ˜ μ „λž΅μ„ μ‚¬μš©

  • ν•˜μ§€λ§Œ, 이 방식은 λͺ¨λ‘ λ°μ΄ν„°λ² μ΄μŠ€μ— μ±…μž„μ„ μ „κ°€ν•˜κ³  λΆ€ν•˜λ₯Ό μœ λ°œν•˜μ—¬ νŠΈλž˜ν”½μ΄ 큰 μ„œλΉ„μŠ€μ—μ„œλŠ” 이런 μ±„λ²ˆ ν™œλ™μ΄ μƒλ‹Ήν•œ λΆ€ν•˜λ‘œ μž‘μš©

ν•΄κ²°μ±…μœΌλ‘œ PrimaryKeyEntity 같은 클래슀λ₯Ό μ΄μš©ν•΄ Entity μƒμ„±μ‹œ Primary Key도 ν•¨κ»˜ 생성

  • ULIDλŠ” 항상 μ€‘λ³΅λ˜μ§€ μ•ŠλŠ”λ‹€.

⚠️ PrimaryKeyEntity λ₯Ό μ΄μš©ν•΄μ„œ μ˜μ†ν™” μ „ Keyλ₯Ό μƒμ„±ν•΄μ£ΌλŠ” λ°©μ‹μ˜ 주의점

  • null인 κ²½μš°μ—λ§Œ μƒˆλ‘œμš΄ Entity둜 νŒλ‹¨ν•˜μ—¬, μ˜μ†ν™” λ˜μ§€μ•Šμ€ 엔티티에 Keyκ°€ ν• λ‹Ήλ˜λ©΄ persist λ˜λŠ”κ²Œ μ•„λ‹Œ merge λ™μž‘

  • κ·ΈλŸ¬λ‹€λ³΄λ‹ˆ ν•œ 번 쑰회 ν›„ μ €μž₯ν•˜λŠ” λΆˆν•„μš”ν•œ 쿼리λ₯Ό μ‹€ν–‰

  • πŸ™†πŸ»β€β™‚οΈ 이것은 Persistable μΈν„°νŽ˜μ΄μŠ€λ₯Ό κ΅¬ν˜„ν•΄μ„œ λΆˆν•„μš”ν•œ 쿼리λ₯Ό λ°©μ§€

    • getId, isNew(μƒˆλ‘œμš΄ μ—”ν‹°ν‹° νŒŒμ•…) ν•¨μˆ˜ 제곡

    • Persistable μΈν„°νŽ˜μ΄μŠ€λ₯Ό κ΅¬ν˜„ν•œ Entityλ₯Ό μ˜μ†ν™” ν•˜λ €ν•˜λ©΄

    • JpaPersistableEntityInformation.isNew ν•¨μˆ˜κ°€ 호좜되며 λ‚΄λΆ€μ μœΌλ‘œ Persistable.isNew ν•¨μˆ˜ 호좜

(4) Property μ ‘κ·Ό μ œμ–΄

βœ… ν”„λ‘œνΌν‹°μ˜ 변경을 μ΅œμ†Œν™”

protected set을 ν†΅ν•œ setter의 μ ‘κ·Ό μ œν•œ

Entity μžμ‹ μ΄λ‚˜ 상속 Entityμ—μ„œλ§Œ 이름 λ³€κ²½ κ°€λŠ₯

  • Entity에 λŒ€ν•΄ allOpen μ˜΅μ…˜μ„ μΆ”κ°€ν–ˆκΈ° λ•Œλ¬Έμ—, open property의 경우 private setter λ―Έν—ˆμš©

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

βœ… 생성일, μˆ˜μ •μΌκ³Ό 같이 변경이 ν•„μš” μ—†λŠ” ν”„λ‘œνΌν‹°

  • λ‹€λ₯Έ ν”„λ‘œνΌν‹°μ²˜λŸΌ setter의 μ ‘κ·Όμ œμ–΄λ₯Ό protected둜 μ„ μ–Έ

  • λ‚΄λΆ€μ μœΌλ‘œλ§Œ 변경을 열어뒀기에 직접 객체 λ‚΄λΆ€μ—μ„œ 변경을 ν•˜μ§€ μ•ŠλŠ” ν•œ μ•ˆμ „

  • 객체 μžμ²΄μ—μ„œ 변경을 μ‹œλ„ν•  수 μžˆμ§€λ§Œ, λΆˆλ³€ ν”„λ‘œνΌν‹°(immutable)도 κ°œλ°œμžκ°€ λ³€κ²½ ν”„λ‘œνΌν‹°(mutable)둜 λ°”κΏ€ 수 μžˆλŠ” 것은 λ™μΌν•˜λ‹€κ³  νŒλ‹¨

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

(5) nullable

λ°μ΄ν„°λ² μ΄μŠ€μ™€ Entity의 μŠ€ν‚€λ§ˆκ°€ 뢈일치 ν•˜λŠ” 경우 νƒ€μž…μ΄ λ‹€λ₯Έκ±΄ JPAμ—μ„œ μž‘μ•„μ„œ λΉŒλ“œ μ‹œμ μ— μ•Œλ €μ€„ 수 μžˆμ§€λ§Œ,

  • nullableν•œ Column을 Entity에 non-nullableν•˜κ²Œ μ„ μ–Έν•œ 경우 Column에 값이 null이라면 λŸ°νƒ€μž„ 였λ₯˜κ°€ λ°œμƒ

이 경우 @Column μ• λ…Έν…Œμ΄μ…˜μ˜ nullable 속성을 λͺ…μ‹œν•˜μ—¬ ν”„λ‘œνΌν‹°μ˜ 속성 νƒ€μž…μ„ μ•Œλ €μ£Όλ©΄, μœ„μ™€ 같은 λŸ°νƒ€μž„ 였λ₯˜λ₯Ό 막아주고, νŒŒμ•…ν•˜κΈ° μ‰¬μ›Œμ§ˆ 수 μžˆλ‹€

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

(6) 외뢀에 λ…ΈμΆœν•˜λŠ” μ—°κ΄€κ΄€κ³„λŠ” Immutable Collection을 λ…ΈμΆœ

JPAμ—μ„œ μ—°κ΄€κ΄€κ³„μ˜ μš”μ†Œ 변경은 λ°μ΄ν„°λ² μ΄μŠ€μ˜ 변경을 μœ λ°œν•˜μ—¬ ν”„λ‘œνΌν‹°λ₯Ό λΆˆλ³€(val)으둜 선언해도 μœ„/λ³€μ‘°κ°€ κ°€λŠ₯

// μœ„λ³€κ°€ κ°€λŠ₯ν•œ MutableList
@OneToMany(fetch = FetchType.LAZY, cascade = [CascadeType.ALL], mappedBy = "writer")
val mutableBoards: MutableList<Board> = mutableListOf()

ν•˜μ§€λ§Œ, 연관관계λ₯Ό λ³€κ²½ν•˜λŠ”κ²ƒμ€ Entity의 νŠΉμ„±μƒ ν•„μš”ν•˜κΈ° λ•Œλ¬Έμ— MutableListλ₯Ό List둜 λ°”κΏ€ 순 μ—†λ‹€. 그렇기에 λ‚΄λΆ€ μ‘°μž‘μš© ν”„λ‘œνΌν‹°μ™€ μ™ΈλΆ€ λ…ΈμΆœμš© ν”„λ‘œνΌν‹°λ₯Ό λ³„λ„λ‘œ 두어 관리할 수 μžˆλ‹€.

@OneToMany(fetch = FetchType.LAZY, cascade = [CascadeType.ALL], mappedBy = "writer")
protected val mutableBoards: MutableList<Board> = mutableListOf()
val boards: List<Board> get() = mutableBoards // μ™ΈλΆ€ λ…ΈμΆœμš© ν”„λ‘œνΌν‹°

boardsλ₯Ό μ‘°νšŒν•˜λŠ” μ‹œμ μ— μ°Έμ‘° μ£Όμ†Œκ°’μ„ κ°€μ§€κ³  μžˆμœΌλ―€λ‘œ 이후 κ²Œμ‹œνŒμ„ μΆ”κ°€ν–ˆμ„λ•Œ λ‚΄μš©μ΄ 연동

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

// boards에 영ν–₯을 λΌμΉ˜μ§€ μ•Šλ„λ‘ μΆ”κ°€
fun writeBoard(board: Board) {
    mutableBoards.add(board)
}

Kotest

Junitκ³Ό Kotest의 차이점 쀑 κ°€μž₯ 큰 뢀뢄은 간결함

class RacingServiceTest : BehaviorSpec({
    Given("μœ νš¨ν•œ νšŸμˆ˜μ™€ μžλ™μ°¨ μ°Έκ°€μžκ°€ μ œκ³΅λœλ‹€.") {
        val info = RacingGameRequest(numberOfRound = 5, carNames = listOf("a", "b", "c", "d", "e"))

        And("μ „μ§„ μ „λž΅μ΄ 항상 전진을 λ°˜ν™˜ν•œλ‹€.") {
            val racingService = RacingService { DirectionType.STRAIGHT }

            When("κ²½μ£Όλ₯Ό μ§„ν–‰ ν–ˆμ„ 경우") {
                val actual = racingService.racing(info)

                Then("전달받은 μžλ™μ°¨ λŒ€μˆ˜λ§ŒνΌ μžλ™μ°¨λ₯Ό μƒμ„±ν•˜κ³  κ²°κ³Όλ₯Ό λ°˜ν™˜ν•œλ‹€.") {
                    actual.racingHistories shouldHaveSize 5
                    actual.racingHistories.mapIndexed { idx, info ->
                        info.records.forEach {
                            it.value shouldBe Distance(idx + 1L)
                        }
                    }
                    actual.winners shouldBe arrayOf(Name("a"), Name("b"), Name("c"), Name("d"), Name("e"))
                }
            }
        }
    }
})

Get Start

build.gradle.kts

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

...

val version = "5.4.0"

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

// 검증 라이브러리
testImplementation 'io.kotest:kotest-assertions-core:$version'
testImplementation 'io.kotest:kotest-property:$version'

IntelliJ ν”ŒλŸ¬κ·ΈμΈ

  • Preference β†’ Plugins β†’ Kotest

Kotest μž₯점듀

βœ… μœ μ—°μ„±

  • λ‹€μ–‘ν•œ ν…ŒμŠ€νŠΈ μŠ€νƒ€μΌμ„ 지원

  • ν–‰μœ„ 주도 ν…ŒμŠ€νŠΈ(BDD)뿐 μ•„λ‹ˆλΌ WordSpec, FunSpec, AnnotationSpec, FreeSpec λ“± λ‹€μ–‘ν•œ μŠ€νƒ€μΌ 지원

βœ… κ°•λ ₯ν•˜κ³  λ‹€μ–‘ν•œ 검증 라이브러리

  • λ³΅μž‘ν•œ ν‘œν˜„μ‹μ΄λ‚˜, μ»¬λ ‰μ…˜, μ˜ˆμ™Έ 등을 κ²€μ¦ν•˜λŠ”λ° μ‚¬μš©ν•  수 μžˆλŠ” 검증 라이브러리(assertion) 제곡

βœ… ν”„λ‘œνΌν‹° 기반 ν…ŒμŠ€νŠΈ

  • ν”„λ‘œνΌν‹° 기반 ν…ŒμŠ€νŠΈλ₯Ό 지원

  • μž„μ˜μ˜ μž…λ ₯값을 λ§Œλ“€μ–΄ μ½”λ“œμ˜ μœ νš¨μ„±μ„ κ²€μ‚¬ν•˜λŠ” λ°©μ‹μœΌλ‘œ λ‹€μ–‘ν•œ 경우의 수λ₯Ό μ²΄κ³„μ μœΌλ‘œ ν…ŒμŠ€νŠΈ

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

βœ… 반볡 및 쀑첩 ν…ŒμŠ€νŠΈ

  • 반볡 및 쀑첩 ν…ŒμŠ€νŠΈλ₯Ό μ§€μ›ν•˜μ—¬ μ—¬λŸ¬ λ³΅μž‘ν•œ ν…ŒμŠ€νŠΈ μΌ€μ΄μŠ€λ₯Ό 더 쉽고 κ°„κ²°ν•˜κ²Œ 관리

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

Testing Style

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

βœ… StringSpec

class StringTest: StringSpec({
		// λ¬Έμžμ—΄μ€ JUnit의 @DisplayName을 λŒ€μ²΄
		"strings.length should return size of string" {
	            "hello".length shouldBe 5
	  }
})

βœ… FunSpec

class FunSpecTest: FunSpec ({
    test("λ¬Έμžμ—΄ 길이 ν…ŒμŠ€νŠΈ") {
        val actual = "abcdefg   "
      
        actual.length shouldBe 10
    }
})

βœ… AnnotationSpec

  • JUnit ν…ŒμŠ€νŠΈ 방식과 μœ μ‚¬

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

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

        actual.length shouldBe 10
    }
}

βœ… DescribeSpec

  • describeκ°€ ν…ŒμŠ€νŠΈ λŒ€μƒμ„ μ§€μΉ­

  • λ‚΄λΆ€μ μœΌλ‘œ μ‘°κ±΄μ΄λ‚˜ 상황을 μ„€λͺ…ν•  λ•ŒλŠ” context μ‚¬μš©

  • ν…ŒμŠ€νŠΈ λ³Έμ²΄μ—λŠ” it 을 μ‚¬μš©ν•΄μ„œ ν…ŒμŠ€νŠΈ μŠ€ν† λ¦¬ν…”λ§

class MyTests : DescribeSpec({
    describe("score") { // ν…ŒμŠ€νŠΈ λŒ€μƒ
        it("start as zero") {
            // test here
        }
        describe("with a strike") { // λ‚΄λΆ€ μ‘°κ±΄μ΄λ‚˜ 상황
            it("adds ten") {
                // test here
            }
            it("carries strike to the next frame") {
                // test here
            }
        }

        describe("for the opposite team") {
            it("Should negate one score") {
                // test here
            }
        }
    }
})
  • 만일 ν…ŒμŠ€νŠΈ λŒ€μƒ disabledλ₯Ό μ μš©ν•˜κ³  싢을 경우 xdescribe μ‚¬μš©

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

βœ… BehaviorSpec

  • ν–‰μœ„ 주도 ν…ŒμŠ€νŠΈ 방식

  • JUnit Nested μ• λ…Έν…Œμ΄μ…˜μ„ ν™œμš©ν•œ 계측 ꡬ쑰 ν…ŒμŠ€νŠΈ 방식과 μœ μ‚¬ν•˜μ§€λ§Œ 더 νŽΈν•˜κ²Œ μ‚¬μš© κ°€λŠ₯

class NameTest : BehaviorSpec({
    Given("Name 객체λ₯Ό 생성할 λ•Œ") {
        When("5κΈ€μž μ΄λ‚΄μ˜ λ¬Έμžμ—΄μ„ μ „λ‹¬ν•˜λ©΄") {
            val actual = Name("12345")

            Then("μ •μƒμ μœΌλ‘œ μƒμ„±λœλ‹€") {
                actual shouldBe Name("12345")
            }
        }

        When("5κΈ€μž μ΄μƒμ˜ λ¬Έμžμ—΄μ„ μ „λ‹¬ν•˜λ©΄") {
            Then("μ˜ˆμ™Έλ₯Ό λ˜μ§„λ‹€") {
                assertThrows<IllegalArgumentException> {
                    Name("123456")
                }
            }
        }
    }
})

Assertion

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

검증 λΌμ΄λΈŒλŸ¬λ¦¬μ—μ„œ μ œκ³΅ν•˜λŠ” μ—¬λŸ¬ 검증 ν•¨μˆ˜

class MatcherTest : StringSpec() {
    init {
        -----------------------String Matchers -------------------------
        // 'shouldBe'λŠ” 동일함을 μ²΄ν¬ν•˜λŠ” Matcher
        "hello world" shouldBe haveLength(11) // lengthκ°€ 11이어야 함을 체크
        "hello" should include("ll") // νŒŒλΌλ―Έν„°κ°€ ν¬ν•¨λ˜μ–΄ μžˆλŠ”μ§€ 체크
        "hello" should endWith("lo") // νŒŒλΌλ―Έν„°κ°€ 끝의 ν¬ν•¨λ˜λŠ”μ§€ 체크
        "hello" should match("he...") // νŒŒλΌλ―Έν„°κ°€ λ§€μΉ­λ˜λŠ”μ§€ 체크
        "hello".shouldBeLowerCase() // μ†Œλ¬Έμžλ‘œ μž‘μ„±λ˜μ—ˆλŠ”μ§€ 체크

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

        list should beEmpty() // μ›μ†Œκ°€ λΉ„μ—ˆλŠ”μ§€ 체크 ν•©λ‹ˆλ‹€.
        list2 shouldBe sorted<String>() // ν•΄λ‹Ή μžλ£Œν˜•μ΄ μ •λ ¬ λ˜μ—ˆλŠ”μ§€ 체크
        map should contain("a", "1") // ν•΄λ‹Ή μ›μ†Œκ°€ ν¬ν•¨λ˜μ—ˆλŠ”μ§€ 체크
        map should haveKey("a") // ν•΄λ‹Ή keyκ°€ ν¬ν•¨λ˜μ—ˆλŠ”μ§€ 체크
        map should haveValue("1") // ν•΄λ‹Ή valueκ°€ ν¬ν•¨λ˜μ—ˆλŠ”μ§€ 체크

				
    }
}

μ½”ν‹€λ¦° 기초, λ‹¨μœ„ ν…ŒμŠ€νŠΈ

코틀린은 μžλ°” ν”Œλž«νΌμ—μ„œ λ™μž‘ν•˜λŠ” μƒˆλ‘œμš΄ ν”„λ‘œκ·Έλž˜λ° μ–Έμ–΄.

그리고 정적 νƒ€μž… μ§€μ • μ–Έμ–΄

Person class

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

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

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }

    public String getNickname() {
        return nickname;
    }

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

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

.

Person Test

@Test
fun `이름 뢙인 인자`() {
    // 클래슀의 속성듀을 선언함과 λ™μ‹œμ— μƒμ„±μžλ₯Ό μ„ μ–Έ
    class Person(val name: String, val age: Int, var nickname: String)

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

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

@Test
fun `널 νƒ€μž…`() {
    // nickname ν•„λ“œλŠ” nallable
    class Person(val name: String, val age: Int, var nickname: String?)

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

@Test
fun `데이터 클래슀`() {
    // 데이터 ν΄λž˜μŠ€λŠ” equals, hashcode, toString, copy κΈ°λŠ₯을 제곡
    data class Person(val name: String, val age: Int, var nickname: String)

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

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

TDD

ν…ŒμŠ€νŠΈ 주도 개발(Test-Driven development, TDD)

짧은 개발 사이클을 λ°˜λ³΅ν•˜λŠ” SW 개발 ν”„λ‘œμ„ΈμŠ€ 쀑 ν•˜λ‚˜

aka. TFD(Test First Development) + λ¦¬νŒ©ν„°λ§

TDD의 ν”„λ‘œκ·Έλž˜λ° μˆœμ„œ

[Red] Write a failing test
 ➑️ [Green] Make the test pass
 ➑️ [Blue] Refactor
 πŸ”
  1. [Red] : μ‹€νŒ¨ν•˜λŠ” μž‘μ€ ν…ŒμŠ€νŠΈ μž‘μ„±(컴파일 μ—λŸ¬ λ¬΄μ‹œ)

  2. [Green] : ν…ŒμŠ€νŠΈκ°€ ν†΅κ³Όλ˜λ„λ‘ μˆ˜μ •. μ–΄λ– ν•œ ν΄λ¦°μ½”λ“œλ„ κ³ λ €ν•˜μ§€ μ•ŠμŒ

  3. [Blue] : Green κ³Όμ •μ—μ„œ λ°œμƒν•œ 업보듀을 λͺ¨λ‘ μ²­μ‚°. μ—¬κΈ°μ„œ ν΄λ¦°μ½”λ“œλ₯Ό μ§„ν–‰

.

Why TDD ❓

(1) κΈ°λŠ₯을 μ›μžμ μœΌλ‘œ λ§Œλ“€κ²Œ λœλ‹€.

  • λ‹¨μœ„ ν…ŒμŠ€νŠΈλŠ” ν”„λ‘œκ·Έλž˜λ°μ—μ„œ κ°€μž₯ μž‘μ€ λ‹¨μœ„λ₯Ό κ°€μ§€λ‹€ λ³΄λ‹ˆ ν…ŒμŠ€νŠΈμ˜ λ²”μœ„λ„ 쒁닀.

  • ν•˜λ‚˜μ˜ λ©”μ„œλ“œκ°€ μ—¬λŸ¬ λ™μž‘μ„ μˆ˜ν–‰ν•˜μ§€ μ•Šκ²Œ 될 ν™•λ₯ μ΄ λ†’μ•„μ§„λ‹€.

(2) μ œμ–΄ν•  수 μžˆλŠ” λ²”μœ„λ₯Ό 늘릴 수 μžˆλ‹€.

  • κΈ°λŠ₯ λͺ…μ„Έλ₯Ό μƒμ„Έν•˜κ²Œ ν•˜κ³  λ‹¨μœ„ ν…ŒμŠ€νŠΈλ₯Ό μž‘μ„±ν•˜λ©΄μ„œ ν…ŒμŠ€νŠΈκ°€ μ„±κ³΅ν•˜λ„λ‘ λ§Œλ“€κΈ° μœ„ν•΄μ„œλŠ” λ©”μ„œλ“œκ°€ λ‚΄κ°€ μ œμ–΄ν•  수 μžˆλ„λ‘ λ§Œλ“€μ–΄μ•Ό ν•˜λŠ”λ°

  • 이 과정을 톡해 λ‚΄κ°€ μ œμ–΄ν•  수 μ—†λŠ” μ˜μ—­μ„ μ΅œμ†Œν™”ν•  수 μžˆλ‹€.

(3) μœ μ§€λ³΄μˆ˜μ„±μ„ 높일 수 μžˆλ‹€.

  • ν”„λ‘œμ νŠΈκ°€ 5λ…„ 이상 μœ μ§€κ°€ 되고, κ΄€λ ¨ μ†ŒμŠ€μ½”λ“œλ§Œ 수백 κ°œκ°€ λ„˜μ–΄κ°€λŠ” 상황이 λ˜μ—ˆμ„ λ•Œ ν•˜λ‚˜μ˜ νŽ˜μ΄μ§€λ₯Ό κ΅¬μ„±ν•˜κΈ° μœ„ν•΄ μ‚¬μš©λ˜λŠ” ν΄λž˜μŠ€κ°€ μˆ˜μ‹­ κ°œκ°€ λ„˜μ„ 것이닀.

  • μˆ˜μ‹­/수백 κ°œμ€‘ ν•˜λ‚˜μ˜ 클래슀의 νŠΉμ • λ©”μ„œλ“œκ°€ λ¬Έμ œκ°€ 생겼닀고 ν•  λ•Œ 이 μ—λŸ¬λ₯Ό μ°ΎλŠ” μ‹œκ°„μ΄ 짧을 땐 λͺ‡ 뢄일 수 μžˆμ§€λ§Œ κΈΈ λ•ŒλŠ” λ©°μΉ λ‘œλ„ λΆ€μ‘±ν•  수 μžˆλ‹€.

  • μ΄λ•Œ ν•΄λ‹Ή ν΄λž˜μŠ€μ— λŒ€ν•œ ν…ŒμŠ€νŠΈ μ½”λ“œκ°€ μž‘μ„±λ˜μ–΄ μžˆλ‹€λ©΄, 배포 μ „ ν…ŒμŠ€νŠΈ λΉŒλ“œ 사이클을 톡해 μ°Ύμ•„λ‚Ό 수 μžˆλ‹€.


FP, μ½”ν‹€λ¦° DSL

ν•¨μˆ˜ν˜• ν”„λ‘œκ·Έλž˜λ°

Why FP❓

λ™μ‹œμ„± 이슈

  • λ°μ΄ν„°μ˜ μƒνƒœλ₯Ό λ³€κ²½ν•˜λŠ” 객체 μ§€ν–₯ ν”„λ‘œκ·Έλž˜λ° λ°©μ‹μœΌλ‘œ λ™μ‹œμ„± 문제λ₯Ό ν•΄κ²°ν•˜λŠ” λ°λŠ” ν•œκ³„ 쑴재

데이터 관리에 λ”°λ₯Έ λΆ€λ‹΄

  • λŒ€μš©λŸ‰ 데이터 처리 μ‹œ 데이터λ₯Ό 객체둜 λ³€ν™˜ν•˜λŠ”λ° 큰 λΆ€λ‹΄

  • λŒ€μš©λŸ‰ 데이터λ₯Ό μ²˜λ¦¬ν•  수 μžˆλŠ” 효츌적인 데이터 ꡬ쑰와 데이터 연산이 ν•„μš”

ν•¨μˆ˜ν˜• ν”„λ‘œκ·Έλž˜λ°μ˜ λͺ¨λ“ˆν™”

  • ν•¨μˆ˜ν˜• ν”„λ‘œκ·Έλž˜λ°μ˜ ν•¨μˆ˜λ₯Ό λͺ¨λ“ˆν™”ν•  경우 수 λ§Žμ€ κ³³μ—μ„œ μž¬μ‚¬μš© κ°€λŠ₯

  • 더 μœ μš©ν•˜κ³ , μž¬μ‚¬μš©μ΄ νŽΈλ¦¬ν•˜κ³ , ꡬ성이 μš©μ΄ν•˜κ³ , ν…ŒμŠ€νŠΈν•˜κΈ° 더 κ°„νŽΈν•œ 좔상화λ₯Ό 제곡

더 λΉ λ₯΄κ²Œ μž‘μ—…ν•΄μ•Ό ν•œλ‹€.

  • 객체에 λŒ€ν•œ λͺ¨λΈλ§μ— λ§Žμ€ μ‹œκ°„μ„ νˆ¬μžν•˜κΈ°λ³΄λ‹€ μ‚¬μš©μž μš”κ΅¬ 사항에 λŒ€ν•΄μ„œ μ΅œμ†Œν•œμœΌλ‘œ μΆ©λΆ„ν•œ μˆ˜μ€€μ„ μœ μ§€ν•˜λ©΄μ„œ

  • λ™μ‹œμ— 변화에 λŒ€ν•΄μ„œλ„ μœ μ—°ν•˜κ²Œ λŒ€μ‘ν•˜λŠ”λ° 도움

λ‹¨μˆœν•¨

  • ν•¨μˆ˜ν˜• ν”„λ‘œκ·Έλž˜λ° 방식을 톡해 ν”„λ‘œκ·Έλž˜λ° μŠ€νƒ€μΌμ„ κ°œμ„ ν•΄ 더 κΉ”λ”ν•œ μ½”λ“œλ‘œ κ΅¬ν˜„

λͺ…λ Ήν˜• ν”„λ‘œκ·Έλž˜λ° vs μ„ μ–Έν˜• ν”„λ‘œκ·Έλž˜λ°

λͺ…λ Ήν˜• ν”„λ‘œκ·Έλž˜λ°

  • ν”„λ‘œκ·Έλž˜λ°μ˜ μƒνƒœμ™€ μƒνƒœλ₯Ό λ³€κ²½μ‹œν‚€λŠ” ꡬ문의 κ΄€μ μœΌλ‘œ μ ‘κ·Όν•˜λŠ” ν”„λ‘œκ·Έλž˜λ° 방식.

  • 컴퓨터가 μ‹€ν–‰ν•  λͺ…령듀을 μ‹€ν–‰ μˆœμ„œλŒ€λ‘œ κ΅¬ν˜„

    • λŒ€λΆ€λΆ„μ˜ 객체 μ§€ν–₯ ν”„λ‘œκ·Έλž˜λ° μ–Έμ–΄κ°€ λͺ…λ Ήν˜• ν”„λ‘œκ·Έλž˜λ° μ–Έμ–΄

    • μ•Œκ³ λ¦¬μ¦˜ 처리 μž‘μ—…μ— μ ν•©ν•œ μ–Έμ–΄

μ„ μ–Έν˜• ν”„λ‘œκ·Έλž˜λ°

  • μ„ μ–ΈμœΌλ‘œλ§Œ ν”„λ‘œκ·Έλž¨μ„ λ™μž‘μ‹œν‚€λŠ” 방식

  • ν”„λ‘œκ·Έλž¨μ„ μ‹€ν–‰ν•˜κΈ° μœ„ν•΄ ꡬ체적인 μž‘λ™ μˆœμ„œλ₯Ό λ‚˜μ—΄ν•˜μ§€ μ•ŠμŒ

  • μ™„μ „ν•˜μ§€ μ•Šμ§€λ§Œ ν•¨μˆ˜ν˜• ν”„λ‘œκ·Έλž˜λ°μ„ ν™œμš©ν•΄ 일정 μˆ˜μ€€μ˜ μ„ μ–Έν˜• ν”„λ‘œκ·Έλž˜λ° κ°€λŠ₯

    • ν•¨μˆ˜ν˜• ν”„λ‘œκ·Έλž˜λ°μ€ μ„ μ–Έν˜• ν”„λ‘œκ·Έλž˜λ°μ˜ ν•œ μ’…λ₯˜

/**
 * λͺ…λ Ήν˜• ν”„λ‘œκ·Έλž˜λ° μŠ€νƒ€μΌ
 */
fun getPoint(customer:Customer): Int {
    for (i in 0..customers.size) {
        val c = customers[i]
        if (customer == c) {
            return c.point
        }
    }
    return NO_DATA
}

/**
 * μ„ μ–Έν˜• ν”„λ‘œκ·Έλž˜λ° μŠ€νƒ€μΌ
 */
fun getPoint(customer:Customer): Int {
    if (isRegisteredCustomer(customer)) {
        return findCustomer(customer).point
    }
    return NO_DATA
}

ν•¨μˆ˜ν˜• ν”„λ‘œκ·Έλž˜λ°μ˜ νŠΉμ§•

  • μž‘μ—…μ„ μ–΄λ–»κ²Œ μˆ˜ν–‰ν•  것인지, How에 집쀑

  • ꡬ체적인 μž‘μ—… 방식은 λΌμ΄λΈŒλŸ¬λ¦¬κ°€ κ²°μ •ν•˜κ³ , 무엇(What)을 μˆ˜ν–‰ν•  것인지에 집쀑

  • side-effectκ°€ λ°œμƒν•˜μ§€ μ•ŠμŒ

λ³€κ²½ λΆˆκ°€λŠ₯ν•œ 값을 ν™œμš©

  • 값이 λ³€κ²½λ˜λŠ” 것을 ν—ˆμš©ν•˜λ©΄ λ©€ν‹° μŠ€λ ˆλ“œ ν”„λ‘œκ·Έλž˜λ°μ΄ μ–΄λ ΅λ‹€.

  • 값을 λ³€κ²½ν•  수 μ—†λŠ” 경우 ν”„λ‘œκ·Έλž¨μ˜ 정확성을 λ†’μ—¬ λ²„κ·Έμ˜ λ°œμƒ κ°€λŠ₯성을 쀄인닀.

일급 κ°μ²΄λ‘œμ„œμ˜ ν•¨μˆ˜

  • ν•¨μˆ˜ν˜• ν”„λ‘œκ·Έλž˜λ°μ—μ„œλŠ” ν•¨μˆ˜κ°€ 일급 객체(first-class citizen)의 역할을 μˆ˜ν–‰

  • ν•¨μˆ˜λ₯Ό 일급 객체둜 ν™œμš©μ΄ κ°€λŠ₯ν•  경우 ν•¨μˆ˜λ₯Ό ν•¨μˆ˜μ˜ 인자둜 λ°›κ±°λ‚˜ ν•¨μˆ˜μ˜ λ°˜ν™˜ κ°’μœΌλ‘œ ν™œμš©ν•˜λŠ” 것이 κ°€λŠ₯

λžŒλ‹€

  • λžŒλ‹€λŠ” 읡λͺ… ν•¨μˆ˜μ˜ λ‹€λ₯Έ ν‘œν˜„

  • 즉, ν•¨μˆ˜λŠ” ν•¨μˆ˜μΈλ° 이름이 μ—†λŠ” 경우λ₯Ό 의미

ν•¨μˆ˜ν˜• ν”„λ‘œκ·Έλž˜λ°μ„ μ—°μŠ΅ν•˜λŠ” 방법

  • ν”„λ‘œκ·Έλž˜λ°μ˜ κΈ°λ³Έ 틀은 객체 μ§€ν–₯ ν”„λ‘œκ·Έλž˜λ°

  • ν•¨μˆ˜ λ‚΄λΆ€ κ΅¬ν˜„μ€ ν•¨μˆ˜ν˜• ν”„λ‘œκ·Έλž˜λ°μ„ μ§€ν–₯

  • 객체의 μƒνƒœ κ΄€λ¦¬λŠ” λΆˆλ³€μ„ μ§€ν–₯

μ‹œν€€μŠ€

μ½”ν‹€λ¦° μ»¬λ ‰μ…˜μ˜ ν•¨μˆ˜λŠ” κ²°κ³Ό μ»¬λ ‰μ…˜μ„ μ¦‰μ‹œ 생성

μ»¬λ ‰μ…˜ ν•¨μˆ˜λ₯Ό μ—°μ‡„ν•˜λ©΄ λ§€ λ‹¨κ³„λ§ˆλ‹€ 계산 쀑간 κ²°κ³Όλ₯Ό μƒˆλ‘œμš΄ μ»¬λ ‰μ…˜μ— μž„μ‹œλ‘œ 보관

  • μ•„λž˜ 연쇄 ν˜ΈμΆœμ€ 리슀트λ₯Ό 2개 생성(map의 결과와 filter의 κ²°κ³Ό)

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

μ‹œν€€μŠ€λ₯Ό μ‚¬μš©ν•˜λ©΄ 쀑간 μž„μ‹œ μ»¬λ ‰μ…˜μ„ μ‚¬μš©ν•˜μ§€ μ•Šκ³ λ„ μ»¬λ ‰μ…˜ 연산을 연쇄

  • μ›μ†Œκ°€ λ§Žμ€ 경우 μ„±λŠ₯이 λˆˆμ— λ„κ²Œ ν–₯상

  • μžλ°” 슀트림과 λ™μΌν•œ κ°œλ…

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

πŸ’‘ 큰 μ»¬λ ‰μ…˜μ— λŒ€ν•΄μ„œ 연산을 μ—°μ‡„μ‹œν‚¬ λ•ŒλŠ” μ‹œν€€μŠ€λ₯Ό μ‚¬μš©ν•˜λŠ” 것을 κ·œμΉ™μœΌλ‘œ μ‚Όμž.

πŸ’‘ μ‹œν€€μŠ€ μ›μ†Œλ₯Ό 인덱슀λ₯Ό μ‚¬μš©ν•΄ μ ‘κ·Όν•˜λŠ” λ“±μ˜ λ‹€λ₯Έ API λ©”μ„œλ“œκ°€ ν•„μš”ν•˜λ‹€λ©΄ μ‹œν€€μŠ€λ₯Ό 리슀트둜 λ°˜ν™˜ν•˜μž.

Example

읡λͺ… 클래슀λ₯Ό λžŒλ‹€λ‘œ μ „ν™˜ν•˜κΈ°

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

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

@Test
fun 이동() {
    val car = Car("jason", 0)
    val actual: Car = car.move(object : MoveStrategy {
        override val isMovable: Boolean = true
    })
    assertEquals(Car("jason", 1), actual)
}

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

/**
 * TO-BE
 */
 // λžŒλ‹€ μ μš©μ„ μœ„ν•΄ ν•¨μˆ˜ν˜• μΈν„°νŽ˜μ΄μŠ€λ‘œ λ³€κ²½
fun interface MoveStrategy {
    fun isMovable(): Boolean
}

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

@Test
fun 이동() {
    val car = Car("jason", 0)
    val actual: Car = car.move { true }
    assertEquals(Car("jason", 1), actual)
}

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

λžŒλ‹€λ₯Ό ν™œμš©ν•΄ 쀑볡 μ œκ±°ν•˜κΈ°

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

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

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

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

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

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

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

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

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

μ½”ν‹€λ¦° DSL

μ½”ν‹€λ¦° DSL

  • λ²”μš© μ–Έμ–΄(=μ½”ν‹€λ¦°)둜 μž‘μ„±λœ ν”„λ‘œκ·Έλž¨μ˜ 일뢀

  • λ²”μš© 언어와 λ™μΌν•œ 문법을 μ‚¬μš©

  • 호좜 κ²°κ³Όλ₯Ό 객체둜 λ³€ν™˜ν•˜κΈ° μœ„ν•΄ λ…Έλ ₯ν•  ν•„μš”κ°€ μ—†μŒ

  • νƒ€μž… μ•ˆμ „μ„±μ„ 보μž₯

  • μ½”ν‹€λ¦° μ½”λ“œλ₯Ό μ›ν•˜λŠ” λŒ€λ‘œ μ‚¬μš© κ°€λŠ₯

코틀린은 κ°„κ²°ν•œ ꡬ문을 지원

  • ν™•μž₯ ν•¨μˆ˜(Extension functions)

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

    @Test
    fun `before`() {
        fun Any.to(other:Any) = Pair(this, other)
        assertEquals(Pair(1, "one"), 1.to("one"))
    }
    
    @Test
    fun `after`() {
        infix fun Any.to(other:Any) = Pair(this, other)
        assertEquals(Pair(1, "one"), 1 to "one")
    
    }
  • μ—°μ‚°μž μ˜€λ²„λ‘œλ”©(Operator overloading)

    @Test
    fun `before`() {
        data class Point(val x: Int, val y: Int) {
            fun plus(other:Point): Point = Point(x + other.x, y + other.y)
        }
    
        assertEquals(Point(1, 3), Point(0, 1).plus(Point(1, 2)))
    }
    
    @Test
    fun `after`() {
        data class Point(val x: Int, val y: Int) {
            operator fun plus(other:Point): Point = Point(x + other.x, y + other.y)
        }
    
        assertEquals(Point(1, 3), Point(0, 1) + Point(1, 2))
    }
  • get λ©”μ„œλ“œμ— λŒ€ν•œ κ΄€λ‘€(Indexed access operator)

    val names = listOf("Aaron", "Park")
    assertEquals("Aaron", names.get(0))
    assertEquals("Aaron", names[0])
  • λžŒλ‹€λ₯Ό κ΄„ν˜Έ λ°–μœΌλ‘œ λΉΌλ‚΄λŠ” κ΄€λ‘€(Passing a lambda to the last parameter)

    @Test
    fun `before`() {
        assertThrows<IllegalStateException> {
            check(false, { -> "Check failed." })
        }
    }
    
    @Test
    fun `after`() {
        assertThrows<IllegalStateException> {
            check(false) { "Check failed." }
        }
    }
  • μˆ˜μ‹  객체 μ§€μ • λžŒλ‹€(Lambda with receiver)

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

μ½”ν‹€λ¦° DSL μ‹€μŠ΅

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

class DslTest {

    /**
     * block : PersonBuilder λ‚΄λΆ€μ˜ ν•¨μˆ˜λ“€μ„ ν˜ΈμΆœν•˜μ—¬ Person 객체의 속성을 μ„€μ •ν•˜λŠ” 데 μ‚¬μš©
     * apply : μˆ˜μ‹  객체(PersonBuilder)에 λŒ€ν•΄ block μ—μ„œ μ •μ˜λœ 섀정을 적용
     *   - 즉, block 은 PersonBuilder 의 μ»¨ν…μŠ€νŠΈμ—μ„œ μ‹€ν–‰
     */
    fun introduce(block:PersonBuilder.() ->Unit): Person {
        return PersonBuilder().apply(block).build()
    }

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

ULID κ΅¬ν˜„ λΌμ΄λΈŒλŸ¬λ¦¬λŠ” λ₯Ό 주둜 μ‚¬μš©

TDD, Clean Code with Kotlin
μžλ°” 개발자λ₯Ό μœ„ν•œ μ½”ν‹€λ¦° μž…λ¬Έ
DIMO Kotlin κ°•μ’Œ
Kotlin in Action
μ•„ν† λ―Ή μ½”ν‹€λ¦°
μ½”ν‹€λ¦° 쿑뢁
객체지ν–₯의 사싀과 μ˜€ν•΄
였브젝트
https://play.kotlinlang.org/
Kotlinμ—μ„œ JPA Entity μ„€κ³„ν•˜κΈ°
ULID Creator
μ½”ν‹€λ¦°μ˜ ν…ŒμŠ€νŠΈλ„κ΅¬ Kotest
JUnit 5 vs Kotest. Part 1: Is it the new way we test?