개발/Kotlin

[Kotlin in action] 6. μ½”ν‹€λ¦° νƒ€μž… μ‹œμŠ€ν…œ

도리 🐟 2021. 8. 13. 16:16

#1. null κ°€λŠ₯μ„± (nullability)

NPEλ₯Ό ν”Όν•  수 있게 돕기 μœ„ν•œ μ½”ν‹€λ¦° νƒ€μž… μ‹œμŠ€ν…œμ˜ νŠΉμ„±

Nullable νƒ€μž…

μ½”ν‹€λ¦° νƒ€μž… μ‹œμŠ€ν…œμ΄ null이 될 수 μžˆλŠ” νƒ€μž…μ„ λͺ…μ‹œμ μœΌλ‘œ μ§€μ›ν•œλ‹€.

νƒ€μž… 이름 뒀에 ?λ₯Ό 뢙이면 κ·Έ νƒ€μž…μ˜ λ³€μˆ˜λ‚˜ ν”„λ‘œνΌν‹°μ— null μ°Έμ‘°λ₯Ό μ €μž₯ν•  수 μžˆλ‹€.
?κ°€ μ—†λŠ” νƒ€μž…μ€ κ·Έ λ³€μˆ˜κ°€ null μ°Έμ‘°λ₯Ό μ €μž₯ν•  수 μ—†λ‹€.

  1. Nullable νƒ€μž…μΈ λ³€μˆ˜μ— λŒ€ν•΄ λ³€μˆ˜.λ©”μ„œλ“œ() 처럼 λ©”μ„œλ“œλ₯Ό 직접 ν˜ΈμΆœν•  μˆ˜λŠ” μ—†λ‹€.
  2. Nullable 값을 NonNull νƒ€μž…μ˜ λ³€μˆ˜μ— λŒ€μž…ν•  수 μ—†λ‹€.
val x: String? = null
val y: String = x //=> Error: Type mismatch: inferred type is String? but String was expected
  1. Nullable νƒ€μž…μ˜ 값을 NonNull νƒ€μž…μ˜ νŒŒλΌλ―Έν„°λ₯Ό λ°›λŠ” ν•¨μˆ˜μ— 전달할 수 μ—†λ‹€.

null이 μ•„λ‹˜μ΄ ν™•μ‹€ν•œ μ˜μ—­μ—μ„œλŠ” ν•΄λ‹Ή 값을 NonNull νƒ€μž…μ˜ κ°’μ²˜λŸΌ μ‚¬μš©ν•  수 μžˆλ‹€.

fun strLenSafe(s: String?): Int = 
    if (s != null) s.length else 0 

>>> val x: String? = null 
>>> println(strLenSafe(x)) 
0 
>>> println(strLenSafe("abc")) 
3

μžλ°”μ˜ Optionalκ³Ό 차이점

Optional은 μ–΄λ–€ 값이 μ •μ˜λ˜κ±°λ‚˜ μ •μ˜λ˜μ§€ μ•Šμ„ 수 μžˆμŒμ„ ν‘œν˜„ν•˜λŠ” 래퍼 νƒ€μž…μ΄λ‹€.
μ½”λ“œκ°€ 지저뢄해지고, λž˜νΌκ°€ μΆ”κ°€λ˜μ–΄ μ‹€ν–‰ μ‹œμ  μ„±λŠ₯에 영ν–₯을 μ€€λ‹€.

μ½”ν‹€λ¦°μ˜ Nullable νƒ€μž…κ³Ό NonNull νƒ€μž…μ€ μ‹€ν–‰ μ‹œμ μ— 같은 νƒ€μž…μ΄λ‹€. λͺ¨λ“  κ²€μ‚¬λŠ” 컴파일 νƒ€μž„μ— μˆ˜ν–‰λ˜μ–΄, λ³„λ„μ˜ μ‹€ν–‰ μ‹œμ  λΆ€κ°€ λΉ„μš©μ΄ 듀지 μ•ŠλŠ”λ‹€.


μ•ˆμ „ν•œ 호좜 μ—°μ‚°μž .?

null 검사와 λ©”μ„œλ“œ ν˜ΈμΆœμ„ ν•œ 번의 μ—°μ‚°μœΌλ‘œ μˆ˜ν–‰ν•œλ‹€.
ν˜ΈμΆœν•˜λ €λŠ” 값이 null이 μ•„λ‹ˆλΌλ©΄ .?은 일반 λ©”μ„œλ“œ 호좜처럼 λ™μž‘ν•œλ‹€.
null이면 이 ν˜ΈμΆœμ€ λ¬΄μ‹œλ˜κ³  null이 λ°˜ν™˜λœλ‹€.

.? 호좜의 κ²°κ³Ό νƒ€μž…μ€ Nullable이닀.

μ—˜λΉ„μŠ€ μ—°μ‚°μž :?

null λŒ€μ‹  μ‚¬μš©ν•  λ””ν΄νŠΈ 값을 지정할 λ•Œ μ‚¬μš©ν•œλ‹€.

fun foo(s: String?) {
    val t: String = s ?: "" // sκ°€ null이면 ""이 κ²°κ³Ό
}

μ—˜λΉ„μŠ€ μ—°μ‚°μžμ˜ μš°ν•­μ— return, throw λ“±μ˜ 연산을 넣을 수 μžˆλ‹€.
(μ½”ν‹€λ¦°μ—μ„œλŠ” return, throw λ“±μ˜ 연산도 식이닀.)
=> 이 경우 μ’Œν•­μ΄ null이면 μ¦‰μ‹œ μ–΄λ–€ 값을 λ°˜ν™˜ν•˜κ±°λ‚˜ μ˜ˆμ™Έλ₯Ό λ˜μ§„λ‹€.

μ•ˆμ „ν•œ 캐슀트 as?

as?λŠ” μ–΄λ–€ 값을 μ§€μ •ν•œ νƒ€μž…μœΌλ‘œ μΊμŠ€νŠΈν•˜λŠ”λ°, λŒ€μƒ νƒ€μž…μœΌλ‘œ μΊμŠ€νŒ…ν•  수 μ—†μœΌλ©΄ null을 λ°˜ν™˜ν•œλ‹€.

class Person(val firstName: String, val lastName: String) { 
    override fun equals(o: Any?): Boolean { 
        val otherPerson = o as? Person ?: return false // νƒ€μž…μ΄ μΌμΉ˜ν•˜μ§€ μ•ŠμœΌλ©΄ false λ°˜ν™˜

        // μ•ˆμ „ν•œ 캐슀트λ₯Ό ν•˜κ³  λ‚˜λ©΄ 슀마트 μΊμŠ€νŠΈλœλ‹€.
        return otherPerson.firstName == firstName && 
            otherPerson.lastName == lastName 
    } 

    override fun hashCode(): Int = 
        firstName.hashCode() * 37 + lastName.hashCode() 
}

null μ•„λ‹˜ 단언 !!

NonNull νƒ€μž…μœΌλ‘œ (κ°•μ œλ‘œ) λ°”κΏ€ 수 μžˆλ‹€.
null인 값에 μ‚¬μš©ν•˜λ©΄ NPEκ°€ λ°œμƒν•œλ‹€.

[μ€‘μš”]

!!λ₯Ό null에 μ‚¬μš©ν•˜μ—¬ λ°œμƒν•œ μ˜ˆμ™Έ μŠ€νƒ νŠΈλ ˆμ΄μŠ€μ—λŠ” μ–΄λ–€ μ‹μ—μ„œ μ˜ˆμ™Έκ°€ λ°œμƒν–ˆλŠ”μ§€ 담겨 μžˆμ§€ μ•Šλ‹€. μ–΄λ–€ 값이 nullμ΄μ—ˆλŠ”μ§€ ν™•μ‹€νžˆ ν•˜κΈ° μœ„ν•΄μ„œλŠ” !!λ₯Ό ν•œ 쀄에 μ—¬λŸ¬ 개 μ“°λŠ” 일은 ν”Όν•˜λŠ” 것이 μ’‹λ‹€.

μ»΄νŒŒμΌλŸ¬κ°€ 검증할 수 μ—†λŠ” 단언을 μ‚¬μš©ν•˜κΈ° λ³΄λ‹€λŠ” 더 λ‚˜μ€ 방법을 μ°Ύμ•„λ³΄λΌλŠ” μ„€κ³„μ˜λ„κ°€ λ‹΄κ²¨μžˆλ‹€.

let ν•¨μˆ˜

let ν•¨μˆ˜λŠ” μžμ‹ μ˜ μˆ˜μ‹  객체λ₯Ό 인자둜 전달받은 λžŒλ‹€μ— λ„˜κΈ΄λ‹€.

let을 μ‚¬μš©ν•˜λŠ” κ°€μž₯ ν”ν•œ μ˜ˆλŠ” Nullable 값을 NonNull만 λ°›λŠ” ν•¨μˆ˜μ— νŒŒλΌλ―Έν„°λ‘œ λ„˜κΈ°λŠ” κ²½μš°μ΄λ‹€.
foo?.let { … } κ³Ό 같이 μ•ˆμ „ν•œ 호좜 ꡬ문을 μ‚¬μš©ν•˜μ—¬ ν˜ΈμΆœν•˜λ©΄, NonNull νƒ€μž…μ„ 인자둜 λ°›λŠ” λžŒλ‹€λ₯Ό let에 μ „λ‹¬ν•œλ‹€.

μ—¬λŸ¬ 값이 null인지 검사해야 ν•œλ‹€λ©΄, let ν˜ΈμΆœμ„ μ€‘μ²©μ‹œμΌœμ„œ μ²˜λ¦¬ν•  μˆ˜λ„ μžˆμ§€λ§Œ μ€‘μ²©μ‹œν‚€λ©΄ μ½”λ“œκ°€ λ³΅μž‘ν•΄μ Έμ„œ μ•Œμ•„λ³΄κΈ° μ–΄λ €μ›Œμ§„λ‹€. 이 κ²½μš°μ—λŠ” 일반적인 if 문을 μ‚¬μš©ν•΄ λͺ¨λ“  값을 ν•œ λ²ˆμ— κ²€μ‚¬ν•˜λŠ” 편이 λ‚«λ‹€.

λ‚˜μ€‘μ— μ΄ˆκΈ°ν™”ν•  ν”„λ‘œνΌν‹°

lateinit λ³€κ²½μžλ₯Ό 뢙이면 ν”„λ‘œνΌν‹°λ₯Ό λ‚˜μ€‘μ— μ΄ˆκΈ°ν™”ν•  수 μžˆλ‹€.
이 ν”„λ‘œνΌν‹°λŠ” 항상 var이어야 ν•œλ‹€.
(val ν”„λ‘œνΌν‹°λŠ” final ν•„λ“œλ‘œ 컴파일되며, μƒμ„±μž μ•ˆμ—μ„œ λ°˜λ“œμ‹œ μ΄ˆκΈ°ν™”ν•΄μ•Ό ν•œλ‹€.)

class MyService { 
    fun performAction(): String = "foo" 
} 

class MyTest { 
    private lateinit var myService: MyService // λ‚˜μ€‘μ— μ΄ˆκΈ°ν™”ν•  NonNull ν”„λ‘œνΌν‹° μ„ μ–Έ

    @Before fun setUp() { 
        myService = MyService() 
    } 

    @Test fun testAction() { 
        Assert.assertEquals("foo", myService.performAction()) 
    } 
}

Nullable νƒ€μž…μ˜ ν™•μž₯ ν•¨μˆ˜

fun String?.isNullOrBlank(): Boolean =
        this == null || this.isBlank()

Nullable νƒ€μž…μ— λŒ€ν•œ ν™•μž₯ ν•¨μˆ˜λ₯Ό μ •μ˜ν•˜λ©΄, Nullable 값에 λŒ€ν•΄ κ·Έ ν™•μž₯ ν•¨μˆ˜λ₯Ό ν˜ΈμΆœν•  수 μžˆλ‹€. ν•΄λ‹Ή ν•¨μˆ˜ λ‚΄μ—μ„œ thisλŠ” null이 될 수 μžˆλ‹€.

νƒ€μž… νŒŒλΌλ―Έν„°μ˜ null κ°€λŠ₯μ„±

νƒ€μž… νŒŒλΌλ―Έν„° Tλ₯Ό ν΄λž˜μŠ€λ‚˜ ν•¨μˆ˜ μ•ˆμ—μ„œ νƒ€μž… μ΄λ¦„μœΌλ‘œ μ‚¬μš©ν•˜λ©΄ 이름 끝에 ?κ°€ 없더라도 TλŠ” null이 될 수 μžˆλŠ” νƒ€μž…μ΄λ‹€.

fun <T> printHashCode(t: T) { // T의 νƒ€μž…μ€ Any?둜 μΆ”λ‘ λœλ‹€.
    println(t?.hashCode())
}

null이 μ•„λ‹˜μ„ λͺ…μ‹œν•˜λ €λ©΄ null이 될 수 μ—†λŠ” upper boundλ₯Ό 지정해야 ν•œλ‹€.

fun <T: Any> printHashCode(t: T) { // TλŠ” null이 될 수 μ—†λŠ” νƒ€μž…μ΄λ‹€.
    println(t.hashCode())
}

νƒ€μž… νŒŒλΌλ―Έν„°λŠ” nul이 될 수 μžˆλŠ” νƒ€μž…μ„ ν‘œμ‹œν•˜λ €λ©΄ λ°˜λ“œμ‹œ λ¬ΌμŒν‘œλ₯Ό νƒ€μž… 이름 뒀에 λΆ™μ—¬μ•Ό ν•œλ‹€.

null κ°€λŠ₯μ„±κ³Ό μžλ°”

μžλ°”μ˜ @Nullable String은 μ½”ν‹€λ¦°μ—μ„œ String?κ³Ό κ°™κ³ , @NotNull String은 Stringκ³Ό κ°™λ‹€.

ν”Œλž«νΌ νƒ€μž…

: μžλ°”μ˜ νƒ€μž… 쀑 코틀린이 null κ΄€λ ¨ 정보λ₯Ό μ•Œ 수 μ—†λŠ” νƒ€μž…

μ½”ν‹€λ¦°μ—μ„œ ν”Œλž«νΌ νƒ€μž…μ„ μ„ μ–Έν•  μˆ˜λŠ” μ—†λ‹€. μžλ°” μ½”λ“œμ—μ„œ κ°€μ Έμ˜¨ νƒ€μž…λ§Œ ν”Œλž«νΌ νƒ€μž…μ΄ λœλ‹€.
! ν‘œκΈ°λŠ” String! νƒ€μž…μ˜ null κ°€λŠ₯성에 λŒ€ν•΄ 아무 정보도 μ—†λ‹€λŠ” λœ»μ΄λ‹€.
μžλ°”μ—μ„œ κ°€μ Έμ˜¨ null 값을 μ½”ν‹€λ¦°μ˜ NonNull λ³€μˆ˜μ— λŒ€μž…ν•˜λ©΄ μ‹€ν–‰ μ‹œμ μ— μ˜ˆμ™Έκ°€ λ°œμƒν•œλ‹€.

#2. μ½”ν‹€λ¦°μ˜ μ›μ‹œ νƒ€μž… (Primitive type)

  • μžλ°”λŠ” μ›μ‹œ νƒ€μž…κ³Ό μ°Έμ‘° νƒ€μž…μ„ κ΅¬λΆ„ν•œλ‹€.
    • μ›μ‹œ νƒ€μž… λ³€μˆ˜μ—λŠ” κ·Έ 값이 직접 λ“€μ–΄κ°€μ§€λ§Œ, μ°Έμ‘° νƒ€μž… λ³€μˆ˜μ—λŠ” λ©”λͺ¨λ¦¬μƒμ˜ 객체 μœ„μΉ˜κ°€ μ €μž₯λœλ‹€.
  • 코틀린은 μ›μ‹œ νƒ€μž…κ³Ό 래퍼 νƒ€μž…μ„ κ΅¬λΆ„ν•˜μ§€ μ•ŠλŠ”λ‹€.
    • 항상 같은 νƒ€μž…μ„ μ‚¬μš©ν•˜λ©°, μ›μ‹œ νƒ€μž…μ˜ 값에 λŒ€ν•΄ λ©”μ„œλ“œλ₯Ό ν˜ΈμΆœν•  수 μžˆλ‹€.

μ›μ‹œ νƒ€μž…: Int, Boolean λ“±

μ‹€ν–‰ μ‹œμ μ— 숫자 νƒ€μž…μ€ κ°€μž₯ 효율적인 λ°©μ‹μœΌλ‘œ ν‘œν˜„λœλ‹€.
λŒ€λΆ€λΆ„μ˜ 경우, μ½”ν‹€λ¦° Int νƒ€μž…μ€ μžλ°”μ˜ int둜 μ»΄νŒŒμΌλœλ‹€.

Int와 같이 null이 될 수 μ—†λŠ” νƒ€μž…μ€ 그에 μƒμ‘ν•˜λŠ” μžλ°” μ›μ‹œ νƒ€μž…μœΌλ‘œ μ»΄νŒŒμΌν•  수 μžˆλ‹€.
λ°˜λŒ€λ‘œ μžλ°” μ›μ‹œ νƒ€μž…μ€ null이 될 수 μ—†μœΌλ―€λ‘œ, μ½”ν‹€λ¦°μ—μ„œ μ‚¬μš©ν•  λ•Œ null이 될 수 μ—†λŠ” νƒ€μž…μœΌλ‘œ μ·¨κΈ‰ν•  수 μžˆλ‹€.

null이 될 수 μžˆλŠ” μ›μ‹œ νƒ€μž…: Int?, Boolean? λ“±

null이 될 수 μžˆλŠ” μ›μ‹œ νƒ€μž…μ€ μžλ°” μ›μ‹œ νƒ€μž…μœΌλ‘œ ν‘œν˜„ν•  수 μ—†λ‹€.
Nullable μ›μ‹œ νƒ€μž…μ€ μžλ°”μ˜ 래퍼 νƒ€μž…μœΌλ‘œ μ»΄νŒŒμΌλœλ‹€.

μ œλ„€λ¦­ 클래슀의 κ²½μš°μ—λ„ 래퍼 νƒ€μž…μ„ μ‚¬μš©ν•œλ‹€.
JVM은 νƒ€μž… 인자둜 μ›μ‹œ νƒ€μž…μ„ ν—ˆμš©ν•˜μ§€ μ•ŠκΈ° λ•Œλ¬Έμ—, μžλ°”μ™€ μ½”ν‹€λ¦° λͺ¨λ‘ μ œλ„€λ¦­ ν΄λž˜μŠ€λŠ” 항상 λ°•μŠ€ νƒ€μž…μ„ μ‚¬μš©ν•΄μ•Ό ν•œλ‹€.

숫자 λ³€ν™˜

코틀린은 ν•œ νƒ€μž…μ˜ 숫자λ₯Ό λ‹€λ₯Έ νƒ€μž…μ˜ 숫자둜 μžλ™ λ³€ν™˜ν•˜μ§€ μ•ŠλŠ”λ‹€.

val i = 1
val l: Long = i.toLong() // λ³€ν™˜ λ©”μ„œλ“œλ₯Ό 직접 ν˜ΈμΆœν•΄μ€˜μ•Ό ν•œλ‹€.

코틀린은 Boolean을 μ œμ™Έν•œ λͺ¨λ“  μ›μ‹œ νƒ€μž…μ— λŒ€ν•œ λ³€ν™˜ ν•¨μˆ˜λ₯Ό μ œκ³΅ν•œλ‹€.
(toInt(), toByte(), …)

μ½”λ“œμ—μ„œ λ™μ‹œμ— μ—¬λŸ¬ 숫자 νƒ€μž…μ„ μ‚¬μš©ν•˜λ €λ©΄ μ˜ˆμƒμΉ˜ λͺ»ν•œ λ™μž‘μ„ ν”Όν•˜κΈ° μœ„ν•΄ 각 λ³€μˆ˜λ₯Ό λͺ…μ‹œμ μœΌλ‘œ λ³€ν™˜ν•΄μ•Ό ν•œλ‹€.

Any, Any?: μ΅œμƒμœ„ νƒ€μž…

Any νƒ€μž…μ΄ μ½”ν‹€λ¦°μ˜ λͺ¨λ“  null이 될 수 μ—†λŠ” νƒ€μž…μ˜ 쑰상 νƒ€μž…μ΄λ©°, Int λ“±μ˜ μ›μ‹œ νƒ€μž…λ„ λͺ¨λ‘ ν¬ν•¨λœλ‹€.
null을 ν¬ν•¨ν•˜λŠ” λͺ¨λ“  값을 λŒ€μž…ν•  λ³€μˆ˜λ₯Ό μ„ μ–Έν•˜λ €λ©΄ Any?λ₯Ό μ‚¬μš©ν•œλ‹€.

λ‚΄λΆ€μ μœΌλ‘œ Any νƒ€μž…μ€ μžλ°”μ˜ Object에 λŒ€μ‘ν•œλ‹€. ν•˜μ§€λ§Œ toString, equals, hashcodeλ₯Ό μ œμ™Έν•œ Object의 wait, notify λ“±μ˜ λ©”μ„œλ“œλŠ” Anyμ—μ„œ μ‚¬μš©ν•  수 μ—†λ‹€. μ‚¬μš©ν•˜λ €λ©΄ java.lang.Object둜 μΊμŠ€νŒ…ν•΄μ•Ό ν•œλ‹€.

Unit: μ½”ν‹€λ¦°μ˜ void

Unit νƒ€μž…μ— μ†ν•œ 값은 단 ν•˜λ‚˜λΏμ΄λ©°, κ·Έ 이름도 Unit이닀.
Unit νƒ€μž…μ˜ ν•¨μˆ˜λŠ” λ¬΅μ‹œμ μœΌλ‘œ Unit 값을 λ°˜ν™˜ν•œλ‹€.

void와 달리 Unit을 νƒ€μž… 인자둜 μ“Έ 수 μžˆλ‹€.

interface Processor<T> {
    fun process(): T
} 

class NoResultProcessor : Processor<Unit> {
    override fun process() {
        // do stuff 
      } 
} 

Nothing νƒ€μž…: 이 ν•¨μˆ˜λŠ” μ ˆλŒ€ μ •μƒμ μœΌλ‘œ λλ‚˜μ§€ μ•ŠλŠ”λ‹€.

Nothing은 ν•¨μˆ˜μ˜ λ°˜ν™˜ νƒ€μž…μ΄λ‚˜ λ°˜ν™˜ νƒ€μž…μœΌλ‘œ 쓰일 νƒ€μž… νŒŒλΌλ―Έν„°μ—λ§Œ μ“Έ 수 μžˆλ‹€.
ν•¨μˆ˜κ°€ κ²°μ½” 정상 μ’…λ£Œλ˜μ§€ μ•ŠμŒ(μ˜ˆμ™Έ λ°œμƒ, ν…ŒμŠ€νŠΈ μ‹€νŒ¨ λ“±)을 ν‘œν˜„ν•  λ•Œ μ‚¬μš©ν•œλ‹€.

#3. μ»¬λ ‰μ…˜κ³Ό λ°°μ—΄

null κ°€λŠ₯μ„±κ³Ό μ»¬λ ‰μ…˜

List<Int?>

  • 리슀트 μžμ²΄λŠ” 항상 null이 μ•„λ‹ˆλ‹€.
  • 리슀트 μ•ˆμ˜ 각 값이 null이 될 수 μžˆλ‹€.

List<Int>?

  • 전체 λ¦¬μŠ€νŠΈκ°€ null이 될 수 μžˆλ‹€.
  • 리슀트 μ•ˆμ—λŠ” null이 λ“€μ–΄κ°ˆ 수 μ—†λ‹€.

-> null이 될 수 μžˆλŠ” 게 μ»¬λ ‰μ…˜μ˜ μ›μ†ŒμΈμ§€ μ»¬λ ‰μ…˜ μžμ²΄μΈμ§€ μ£Όμ˜ν•΄μ•Ό ν•œλ‹€.

null이 될 수 μžˆλŠ” 값을 κ°–λŠ” μ»¬λ ‰μ…˜μ—μ„œ null 값을 κ±ΈλŸ¬λ‚΄λŠ” 경우, filterNotNull μ΄λΌλŠ” ν•¨μˆ˜λ₯Ό μ΄μš©ν•˜λ©΄ νŽΈλ¦¬ν•˜λ‹€.

읽기 μ „μš©κ³Ό λ³€κ²½ κ°€λŠ₯ν•œ μ»¬λ ‰μ…˜

  • Collection : μ›μ†Œλ₯Ό μΆ”κ°€ν•˜κ±°λ‚˜ μ œκ±°ν•˜λŠ” λ©”μ„œλ“œκ°€ μ—†λ‹€.
  • MutableCollection: μ›μ†Œ μΆ”κ°€, μ‚­μ œ λ“± λ©”μ„œλ“œλ₯Ό μ œκ³΅ν•œλ‹€.