Null Safe

์ž๋ฐ”์™€ ๋‹ค๋ฅด๊ฒŒ ์ฝ”ํ‹€๋ฆฐ์€ Null์—๋Œ€ํ•ด ๊ต‰์žฅํžˆ ๊นŒ๋‹ค๋กญ๋‹ค.

์ž๋ฐ”๋Š” ํ•จ์ˆ˜๊ฐ€ ๊ฐ’์ด Null์ธ์ง€ ๋ณด์žฅ์„ ํ•˜์ง€ ๋ชป๋‹ค. @Nullable, @NotNull ๋“ฑ์˜ ์–ด๋…ธํ…Œ์ด์…˜์œผ๋กœ ์ฒ˜๋ฆฌ๋ฅผ ํ•˜๊ธฐ๋Š” ํ•˜์ง€๋งŒ ์œ ์ง€๋ณด์ˆ˜๋‚˜ ๊ฐ€๋…์„ฑ ์ธก๋ฉด์—์„œ ์ œ๊ณต๋˜๋Š” ๋ถ€๋ถ„์ด๊ณ  ์ปดํŒŒ์ผ๋Ÿฌ๊ฐ€ ์‹ค์ œ๋กœ ์—๋Ÿฌ๋ฅผ ๋ฑ‰์–ด์ฃผ์ง€๋Š” ๋ชปํ•œ๋‹ค. ํ•˜์ง€๋งŒ ์ฝ”ํ‹€๋ฆฐ์—์„œ๋Š” ์ปดํŒŒ์ผ๋Ÿฌ๊ฐ€ ์—๋Ÿฌ๋ฅผ ์žก์•„์ค€๋‹ค.

์ฝ”ํ‹€๋ฆฐ์—๋Š” ์•ˆ์ „ํ•œ ํ˜ธ์ถœ(safe call) ?. ์—ฐ์‚ฐ์ž์™€ ์—˜๋น„์Šค ์˜คํผ๋ ˆ์ดํ„ฐ๋ผ๊ณ  ํ•˜๋Š” ?: ์—ฐ์‚ฐ์ž๊ฐ€ ์กด์žฌํ•œ๋‹ค. (์—˜๋น„์Šค ํ”„๋ ˆ์Šฌ๋ฆฌ์˜ ๋จธ๋ฆฌ๋ชจ์–‘์„ ๋‹ฎ์•˜๋‹คํ•ด์„œ ์—˜๋น„์Šค๋‹ค...)

์ด ? ๊ฐ€ ๋ถ™์€๊ฒฝ์šฐ์—๋งŒ ์ปดํŒŒ์ผ๋Ÿฌ๊ฐ€ Null์„ ํ—ˆ์šฉํ•œ๋‹ค.

? ๊ฐ€ ๋ถ™์ง€์•Š์€, Nullable ํ•˜์ง€ ์•Š๋‹ค๊ณ  ๋˜์–ด์žˆ๋Š” ๊ณณ์— Nullable ํ•œ ๊ฐ’์„ ํ• ๋‹นํ•ด ์ฃผ๋ ค๊ณ  ํ•˜๋ฉด ์ปดํŒŒ์ผ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค.

์ด๋Ÿฐ์‹์œผ๋กœ ์ปดํŒŒ์ผ๋Ÿฌ๊ฐ€ ์—๋Ÿฌ๋ฅผ ์žก์•„์ฃผ๊ธฐ ๋•Œ๋ฌธ์— ๋Ÿฐํƒ€์ž„์—์„œ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋Š” NPE ๋ฅผ ๋ฐฉ์ง€ํ•  ์ˆ˜ ์žˆ๋‹ค.

var str : String = "Hello World"
str = null
//error

var str_1: String? = "Hello World"
str_1 = null
//Nullable

์ด๋ ‡๊ฒŒ ์ฝ”๋“œ์ž‘์„ฑ๋‹จ์—์„œ ์ปดํŒŒ์ผ๋Ÿฌ๊ฐ€ ์žก์•„์ฃผ๊ธฐ ๋•Œ๋ฌธ์— ํ˜‘์—…์„ ํ•  ๋•Œ๋„ ๋‹ค๋ฅธ์‚ฌ๋žŒ์ด ์ง  ๋ถ€๋ถ„์˜ ์ฝ”๋“œ์—์„œ ๊ฐ’์„ ๋ฐ›์•„์•ผ ํ•  ๋•Œ ์ฝ”ํ‹€๋ฆฐ ์—์„œ๋Š” ๋ฐ›์•„์•ผ ํ•  ๊ฐ’์ด null ์ด ์˜ฌ ์ˆ˜ ์žˆ๋Š” ๊ฐ’์ธ์ง€ null ์ด ์˜ฌ ์ˆ˜ ์—†๋Š” ๊ฐ’์ธ์ง€ ๋ช…์‹œ์ ์œผ๋กœ ์•Œ ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ๊ตณ์ด null ์ฒดํฌ๋ฅผ ํ•˜๋Š” ์ฝ”๋“œ๋ฅผ ์ถ”๊ฐ€ํ•ด ์ค„ ํ•„์š”๊ฐ€ ์—†์–ด์ง„๋‹ค.

var nullable: String? = "Kotlin"
fun nullSafe(str: String?): String? {
    return str
}

Nullable ํ•˜๊ฒŒ ์„ ์–ธ์„ ํ–ˆ๋‹ค๋ฉด ๊ทธ ๊ฐ์ฒด์— ์ ‘๊ทผ์„ ํ• ๋•Œ๋„ ?. ์œผ๋กœ ์ ‘๊ทผ์„ ํ•ด์•ผํ•œ๋‹ค. safe-call ์—ฐ์‚ฐ์ž๋Š” ์ด ์—ฐ์‚ฐ์ž๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฐ์ฒด๊ฐ€ null ๊ฐ’์ด ์•„๋‹Œ ๊ฒฝ์šฐ์— ์—ฐ์‚ฐ์ž ๋’ค์˜ ๋ฌธ์žฅ์„ ์ˆ˜ํ–‰ํ•œ๋‹ค. null ๊ฐ’์ผ ๊ฒฝ์šฐ์—๋Š” ๋’ค์˜ ๋ฌธ์žฅ์„ ์ˆ˜ํ–‰ํ•˜์ง€ ์•Š๊ณ  null ๊ฐ’์„ ๋ฐ˜ํ™˜ํ•œ๋‹ค. -> ์ฆ‰, null ์ธ ๊ฐ์ฒด์˜ property ๋ฅผ ์ฐธ์กฐํ•˜๊ฑฐ๋‚˜, ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•˜๋Š” ์ผ์„ ๋ฐฉ์ง€ํ•  ์ˆ˜ ์žˆ๋‹ค.

var str: String? = "Hello World!"
str.length
//error

str?.length
//correct

// bar๊ฐ€ null์ด ์•„๋‹ ๊ฒฝ์šฐ์—๋งŒ ํ•ด๋‹น ๊ฐ’์„ ๋Œ€์ž…, ๊ทธ๋ ‡์ง€ ์•Š์€ ๊ฒฝ์šฐ null์„ foo์— ๋Œ€์ž…ํ•œ๋‹ค.
val foo = bar?.baz

//foo๊ฐ€ null์ด ์•„๋‹ ๊ฒฝ์šฐ์—๋งŒ bar() ํ˜ธ์ถœ
foo?.bar()



//java
public class Contact {

    @NotNull
    String name;

    @Nullable
    Address address;

}

class Address {

    @NotNull
    String line1;

    @Nullable
    String line2;

}

// Address.line1์€ null๊ฐ’์„ ํ—ˆ์šฉํ•˜์ง€ ์•Š์ง€๋งŒ, 
// address๊ฐ€ null์ธ ๊ฒฝ์šฐ null์„ ๋ฐ˜ํ™˜ํ•˜๊ฒŒ ๋˜๋ฏ€๋กœ ๊ฐ’ line์˜ ํƒ€์ž…์€ null๊ฐ’์„ ํ—ˆ์šฉํ•ด์•ผ ํ•œ๋‹ค.
val line: String? = Contact().address?.line1

// ์ฃผ์†Œ๊ฐ€ ์—†๊ฑฐ๋‚˜ line2๊ฐ€ ์—†์„ ๊ฒฝ์šฐ ๊ธฐ๋ณธ๊ฐ’์ธ "No Address"๋ฅผ ๋ฆฌํ„ด
val line: String = Contact().address?.line2 ?: "No Address"


str!!.length
// ์ ˆ๋Œ€ Null์ด ์—†์„๊ฒฝ์šฐ์— ๊ฐ•์ œํ˜ธ์ถœํ•˜๋Š” ๋ฐฉ๋ฒ•
// ์ฃผ์˜ํ•ด์•ผ๋ ์ ์ด Nullable๋กœ ์„ ์–ธํ•œ ๋ณ€์ˆ˜์— !!๋กœ ์ ‘๊ทผ์„ ํ•˜๋ฉด ์ปดํŒŒ์ผ์—๋Š” ๋ฌธ์ œ๊ฐ€ ์—†์„๊ฒƒ์ด๋‹ค.
// ํ•˜์ง€๋งŒ ๋Ÿฐํƒ€์ž„์—์„œ ์ด Nullableํ•œ ๊ฐ’์—๋‹ค๊ฐ€ Null์ด ๋“ค์–ด๊ฐ€๋Š”๋ฐ Null์ด ์•„๋‹ˆ๋ผ๊ณ  !!๋กœ ์ ‘๊ทผ์„ ํ•˜๋ฉด
// ๋Ÿฐํƒ€์ž„์ค‘์— NPE ๋ฟœ๋Š”๋‹ค.
// ๊ณ ๋กœ !!๋Š” ๋‚ด๊ฐ€ ๊ตณ์ด Null์ด ์ ˆ๋Œ€ ์˜ค์ง€ ์•Š์„๊ฒƒ์ด๋‹ค ๋ผ๊ณ  ๋ณด์žฅํ•  ์ˆ˜ ์žˆ๋Š” ๋ถ€๋ถ„์—๋งŒ ์‚ฌ์šฉ์„ ํ•ด์•ผํ•œ๋‹ค.

val foo: Foo? = nullable...

// ๊ฐ’ foo๋Š” null๊ฐ’์„ ํฌํ•จํ•˜์ง€ ์•Š์Œ์„ ๋ณด์ฆ.
val nonNullFoo: Foo = foo!!

// ๊ฐ’ foo๊ฐ€ null๊ฐ’์ด ์•„๋‹˜์„ ๋ณด์ฆํ•˜๋ฉด์„œ bar()ํ•จ์ˆ˜ ํ˜ธ์ถœ
foo!!.bar()

// ๊ฐ’ foo๊ฐ€ null์ด ์•„๋‹˜์„ ๋ณด์ฆํ•˜๋ฉด์„œ baz ํ”„๋กœํผํ‹ฐ ์ ‘๊ทผ
foo!!.baz

Nullableํ•œ ๊ฐ’์— ์ ‘๊ทผ์„ ํ• ๋•Œ Null์ผ ๊ฒฝ์šฐ์—๋Š” Default๊ฐ’์„ ์ฃผ๊ณ ์‹ถ์„๋•Œ ์—˜๋น„์Šค ์˜คํผ๋ ˆ์ดํ„ฐ๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค.

if else ๋กœ ํŒ๋ณ„ํ•ด๋„ ๋˜์ง€๋งŒ ์—˜๋น„์Šค ์˜คํผ๋ ˆ์ดํ„ฐ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ํŽธ์ด ๊ฐ€๋…์„ฑ์ธก๋ฉด์—์„œ ํ›จ์”ฌ ์ข‹๋‹ค. ์ž๋ฐ”์˜ ์‚ผํ•ญ์—ฐ์‚ฐ์ž์™€ ๋น„์Šทํ•˜๊ฒŒ ์ƒ๊ฒผ๋‹ค. ์ฐธ๊ณ ๋กœ ์ฝ”ํ‹€๋ฆฐ์—๋Š” ์‚ผํ•ญ์—ฐ์‚ฐ์ž๊ฐ€ ์—†๋‹ค.

var str: String? = "Hello World!"
str?.length ?: -1
// ์•ž์— ๊ฐ’์ด Null์ด ์•„๋‹ˆ๋ผ๋ฉด ๊ทธ๋ƒฅ Nullable์˜ length ๊ฐ’์„ ๊ฐ€์ ธ์˜ค๊ณ ,
// Null์ด๋ผ๋ฉด Default๋กœ ์„ค์ •ํ•ด๋‘” ๊ฐ’์„ ๊ฐ€์ ธ์˜จ๋‹ค. (?: ์˜ ๋’ค์˜ ๊ฐ’.)
// ์ด๋Ÿฐ์‹์œผ๋กœ ๋„์ฒ˜๋ฆฌ๋ฅผ ์‰ฝ๊ฒŒ ํ•  ์ˆ˜ ์žˆ๋‹ค.

fun generateMapWithAddress(address: String) : Image? {
    // ๊ฒ€์ƒ‰๊ฒฐ๊ณผ๊ฐ€ ์—†์„๊ฒฝ์šฐ Exception ๋ฐœ์ƒ
    val postal = findPostalCode(address) ?: throw IllegalStateException()
}

as? ์—ฐ์‚ฐ์ž. ์ง€์›๋˜์ง€ ์•Š๋Š” ์ž๋ฃŒํ˜•์œผ๋กœ ๋ณ€ํ™˜์„ ์‹œ๋„ํ•˜๋Š” ๊ฒฝ์šฐ ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค.

val foo: String = "foo"

//java.lang.ClassCastException ๋ฐœ์ƒ
val bar: Int = foo as Int

//์ž๋ฐ”์—์„œ๋Š” ์ง€์›๋˜์ง€ ์•Š๋Š” ์ž๋ฃŒํ˜•์œผ๋กœ ๋ณ€ํ™˜์„ ์‹œ๋„ํ•  ๊ฐ€๋Šฅ์„ฑ์ด ์žˆ๋Š” ๋ถ€๋ถ„์„
//try-catch ๋ธ”๋ก์œผ๋กœ ๊ฐ์‹ธ ์ฒ˜๋ฆฌํ•ด์•ผ ํ•˜์ง€๋งŒ,
//์ฝ”ํ‹€๋ฆฐ์—์„  as? (์•ˆ์ „ํ•œ๋ณ€ํ™˜) ์—ฐ์‚ฐ์ž๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ด ๋ฌธ์ œ๋ฅผ ๊ฐ„ํŽธํ•˜๊ฒŒ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ๋‹ค.
//as? ์—ฐ์‚ฐ์ž๋Š” ํ˜•๋ณ€ํ™˜์ด ์‹คํŒจํ•  ๊ฒฝ์šฐ ์˜ˆ์™ธ๋ฅผ ๋ฐœ์ƒ์‹œํ‚ค๋Š” ๋Œ€์‹ ์— null๊ฐ’์„ ๋ฐ˜ํ™˜ํ•œ๋‹ค.
//๋”ฐ๋ผ์„œ ๋ณ€ํ™˜๋˜๋Š” ๊ฐ’์„ ํ†ตํ•ด ๋ณ€ํ™˜๊ฒฐ๊ณผ๋ฅผ ๋ฐ”๋กœ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.
//null๊ฐ’์ด ์˜ฌ ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ ๋ฐ›๋Š”์ชฝ์˜ ์ž๋ฃŒํ˜•์„ Nullable๋กœ ํ•ด์ฃผ์–ด์•ผ ํ•œ๋‹ค.

val foo: String = "foo"

//bar๊ฐ€ null์„ ํ—ˆ์šฉํ•˜๋„๋ก Int?๋กœ ์ •์˜ํ•œ๋‹ค.
//ํ˜•๋ณ€ํ™˜์— ์‹คํŒจํ•˜๋ฏ€๋กœ bar์—๋Š” null์ด ํ• ๋‹น๋œ๋‹ค.
val bar: Int? = foo as? Int

//์‹คํŒจ์˜ ๊ฒฝ์šฐ null์„ ๋ฐ˜ํ™˜ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์—˜๋น„์Šค ์—ฐ์‚ฐ์ž์™€ ํ•จ๊ป˜ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.
val bar: Int = foo as? Int ?: 0 //ํ˜•๋ณ€ํ™˜ ์‹คํŒจ์˜ ๊ฒฝ์šฐ ๊ธฐ๋ณธ๊ฐ’์„ 0์œผ๋กœ ์ง€์ •.

์ž๋ฐ”๋กœ ์ž‘์„ฑ๋œ ํด๋ž˜์Šค๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ null ๊ฐ’์„ ํ—ˆ์šฉํ•˜๋„๋ก ์ฒ˜๋ฆฌ๋˜๋ฉฐ, ์ฝ”ํ‹€๋ฆฐ์—์„  platform type์ด๋ผ ๋ถ€๋ฅธ๋‹ค. ํ”Œ๋žซํผ ํƒ€์ž…์€ Type!๊ณผ ๊ฐ™์€ ํ˜•ํƒœ๋กœ ํ‘œ์‹œ๋œ๋‹ค. ํ”Œ๋žซํผ ํƒ€์ž…์€ ์ฝ”ํ‹€๋ฆฐ์—์„œ ์ž๋ฐ”๋กœ ์ž‘์„ฑํ•œ ํด๋ž˜์Šค๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ์— ์ž๋™์œผ๋กœ ์ง€์ •๋˜๋Š” ํƒ€์ž…์œผ๋กœ, ์ด ํƒ€์ž…์€ ๊ฐœ๋ฐœ์ž๊ฐ€ ์ง์ ‘ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†๋‹ค.

//java
class Person {
    String name; 
    
    public String getName() {
        return name;
    }
}
//kotlin
val person: Person = ...

// n1์€ null๊ฐ’์„ ํ—ˆ์šฉํ•˜์ง€ ์•Š๋Š”๋‹ค.
val n1: String = person.name

// n2๋Š” null๊ฐ’์„ ํ—ˆ์šฉํ•œ๋‹ค.
val n2: String? = person.name

// ์ด๋Ÿฐ ํŠน์ง• ๋•Œ๋ฌธ์— ํ”Œ๋žซํผํƒ€์ž… ๊ฐ์ฒด๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ๋Š” ํ•ญ์ƒ ๊ฐ์ฒด์˜ null๊ฐ’ ์—ฌ๋ถ€๋ฅผ ํ™•์ธํ•ด์•ผ๋งŒ ํ•œ๋‹ค. -> NPE
// ์ฝ”ํ‹€๋ฆฐ์—์„œ๋Š” ์ด๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ์ž๋ฐ”์˜ annotation์„ ์ธ์‹ํ•ด ๊ฐ์ฒด์˜ null ํ—ˆ์šฉ์—ฌ๋ถ€๋ฅผ ํŒ๋‹จํ•œ๋‹ค.
//java
class Person {
    @Nullable
    String name; //์ด๋Ÿฐ ์‹์œผ๋กœ ์–ด๋…ธํ…Œ์ด์…˜์„ ๋ถ™์—ฌ์ฃผ๋ฉด nameํ•„๋“œ๋Š” ์ฝ”ํ‹€๋ฆฐ์—์„œ nullableํ•œ ํ”„๋กœํผํ‹ฐ(String?)์œผ๋กœ ์ธ์‹๋œ๋‹ค.
    
    public String getName() {
        return name;
    }
}
//kotlin
//์‹คํŒจ
val n1: String = person.name

//์„ฑ๊ณต
val n2: String? = person.name

Last updated