# Null Safe

자바와 다르게 코틀린은 Null에대해 굉장히 까다롭다.

자바는 함수가 값이 Null인지 보장을 하지 못다. @Nullable, @NotNull 등의 어노테이션으로 처리를 하기는 하지만 유지보수나 가독성 측면에서 제공되는 부분이고 컴파일러가 실제로 에러를 뱉어주지는 못한다. 하지만 코틀린에서는 컴파일러가 에러를 잡아준다.

코틀린에는 **안전한 호출**(safe call) **?.** 연산자와 **엘비스 오퍼레이터**라고 하는 **?:** 연산자가 존재한다. ~~*(엘비스 프레슬리의 머리모양을 닮았다해서 엘비스다...)*~~

이 ? 가 붙은경우에만 컴파일러가 Null을 허용한다.

? 가 붙지않은,  Nullable 하지 않다고 되어있는 곳에 Nullable 한 값을 할당해 주려고 하면 컴파일 에러가 발생한다.

이런식으로 컴파일러가 에러를 잡아주기 때문에 런타임에서 발생할 수 있는 NPE 를 방지할 수 있다.

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

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

이렇게 코드작성단에서 컴파일러가 잡아주기 때문에 협업을 할 때도 다른사람이 짠 부분의 코드에서 값을 받아야 할 때 코틀린 에서는 받아야 할 값이 null 이 올 수 있는 값인지 null 이 올 수 없는 값인지 명시적으로 알 수 있기 때문에 굳이 null 체크를 하는 코드를 추가해 줄 필요가 없어진다.

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

Nullable 하게 선언을 했다면 그 객체에 접근을 할때도 ?. 으로 접근을 해야한다. safe-call 연산자는 이 연산자를 사용하는 객체가 null 값이 아닌 경우에 연산자 뒤의 문장을 수행한다. null 값일 경우에는 뒤의 문장을 수행하지 않고 null 값을 반환한다. -> 즉, null 인 객체의 property 를 참조하거나, 함수를 호출하는 일을 방지할 수 있다.

```kotlin
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값을 주고싶을때 엘비스 오퍼레이터를 사용한다.&#x20;

if else 로 판별해도 되지만 엘비스 오퍼레이터를 사용하는 편이 가독성측면에서 훨씬 좋다. 자바의 삼항연산자와 비슷하게 생겼다. 참고로 코틀린에는 삼항연산자가 없다.

```kotlin
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? 연산자. 지원되지 않는 자료형으로 변환을 시도하는 경우 예외가 발생한다.

```kotlin
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
//java
class Person {
    String name; 
    
    public String getName() {
        return name;
    }
}
```

```kotlin
//kotlin
val person: Person = ...

// n1은 null값을 허용하지 않는다.
val n1: String = person.name

// n2는 null값을 허용한다.
val n2: String? = person.name

// 이런 특징 때문에 플랫폼타입 객체를 사용할 때는 항상 객체의 null값 여부를 확인해야만 한다. -> NPE
// 코틀린에서는 이를 해결하기 위해 자바의 annotation을 인식해 객체의 null 허용여부를 판단한다.
```

```java
//java
class Person {
    @Nullable
    String name; //이런 식으로 어노테이션을 붙여주면 name필드는 코틀린에서 nullable한 프로퍼티(String?)으로 인식된다.
    
    public String getName() {
        return name;
    }
}
```

```kotlin
//kotlin
//실패
val n1: String = person.name

//성공
val n2: String? = person.name
```
