코틀린 입문 스터디 (7) Nullability

mook2_y2
8 min readFeb 24, 2019

--

스터디파이 코틀린 입문 스터디 (https://studypie.co/ko/course/kotlin_beginner) 관련 자료입니다.

코틀린 입문반은 Kotlin을 직접 개발한 개발자가 진행하는 Coursera 강좌인 “Kotlin for Java Developers” (https://www.coursera.org/learn/kotlin-for-java-developers) 를 기반으로 진행되며 아래는 본 강좌 요약 및 관련 추가 자료 정리입니다.

목차

(1) Introduction

(2) From Java to Kotlin

(3) Basics

(4) Control Structures

(5) Extensions

(6) 실습 : Mastermind game

(7) Nullability

(8) Functional Programming

(9) 실습 : Mastermind in a functional style, Nice String, Taxi Park

(10) Properties

(11) Object-oriented Programming

(12) Conventions

(13) 실습 : Rationals, Board

(14) Inline functions

(15) Sequences

(16) Lambda with Receiver

(17) Types

(18) 실습 : Game 2048 & Game of Fifteen

1. Nullable types (10m)

  • Null은 이를 고안한 Tony Hoare 조차 “Billion Dollar Mistake”라고 언급할 정도로 NPE (NullPointerException)라는 골치 아픈 문제를 만들었다. (관련 링크 : null과 NullReferenceException) 이에 대한 Kotlin을 포함한 최신 언어들의 개선 방안은 이 문제를 Run-time Exception에서 Compile-time error로 바꾸는 것이다. 이를 통해 컴파일 과정에서 문제를 파악하여 사전에 예방할 수 있다.
  • Nullable type인 변수를 역참조 (dereference) 하고자 할 경우 (ex: Non-nullable 에 대해 정의된 확장함수(Extension)나 , Non-nullable 타입을 인자로 받는 함수) 컴파일 에러가 발생한다. 이를 처리하기 위해서는 조건문을 사용하여 명시적으로 null 여부를 체크하거나 (ex: if(variable != null) { }), Safe access expression(?)을 쓰거나, Not-null assertion(!!)을 사용해야 한다.
  • 한편 조건문을 통해 null 체크한 뒤에 컴파일 에러가 발생하지 않는 것은 smart casts 에 따라 자동으로 Non-nullable로 형변환되기 때문이다. (관련 링크 : Kotlin 키워드 및 연산자 해부 Part 1 6번 항목)
  • Safe access expression(?)을 쓸 경우 역참조하는 변수가 null인 경우 null을 반환한다. 이에 대한 디폴트값을 주기 위해서는 Elvis operator(?:)를 사용한다. Elvis operator는 Groovy 언어에서 영감을 받아 차용된 된 개념이다.
  • Elvis operator(?:)는 Kotlin 연산자 우선순위(Operator precedence)에 따라 동작하므로 다른 연산자와 함께 사용할 때 주의해야 한다. (관련 링크 : 코틀린 공식 문서 — Grammer의 Expressions > Precedence 항목)
  • Not-null assertion(!!)의 경우 개발자가 논리적으로 절대 null이 발생할 가능성이 없다고 확신할 때를 제외하면 가능한 피하는 것이 좋다. 이는 결국 기존 Java의 문제점인 Run-time 상에서의 NPE를 허용하는 것이기 때문이다. 하지만 그럼에도 Not-null assertion(!!) 이 좋은 것은 Java에 비해 가독성 측면에서 NPE가 발생할 가능성이 있는 지점을 명시적으로 보여주기 때문이다.
  • Kotlin은 Int, Char, Boolean 등에 대해 Primitive Type과 Wrapper class(Reference Type)를 구분하지 않는다. Java처럼 intInteger를 구분하지 않으며 오직 Int만 가지고 컴파일시에 자동으로 상황에 맞게 Primitive Type 또는 Wrapper class로 변환한다. 한편, Java에서 Primitive Type은 Null을 가질 수 없으므로 Int?와 같이 Nullable 타입으로 지정하는 경우 컴파일시에 무조건 Wrapper Class로 변환된다. (관련 링크 : 코틀린 원시타입 — Unit, Nothing, Int, Boolean)

2. Nullable types under the hood (5m)

  • Kotlin에서 Nullable type과 Non-nullable type은 Java8에서 도입된 Optional 클래스가 아니라, RetentionPolicy.CLASS (런타임시 적용되지 않음)인 Java annotation 형태로 (@Nullable,@NotNull) 변환되어 적용된다. 이에 따라 성능 오버헤드가 발생하지 않으며 Null 문제를 해결할 수 있다는 장점이 있다. (관련 링크 : Java annotation 기본설명, 코틀린 공식 문서-Calling Java code from Kotlin의 nullability annotation 부분)
  • List<Int?>는 non-nullable list of nullable values 이고, List<Int>?는 nullable list of non-nullable values 이다.

3. Checking whether string is null or empty 예제 및 답안 (5m + 3m)

  • Extension을 정의하는 String은 nullable일까 non-nullable일까?
  • Extension의 반환 타입은 무엇일까? (답변 영상에서는 1주차에서 배운 Function with expression body를 통해 타입 추론으로 반환 타입 명시를 생략하였으나 연습시 우선 반환 타입을 명시하고 시작해봅시다.)
  • 1주차에서 배운 Smart Casts를 활용하여 코드를 간소화하기 위해서 비교문 순서를 어떻게 하는게 좋을까?

4. Safe casts (2m) & Safe casts 예제 및 답안 (5m + 1m)

  • Unsafe casts (as) : as 키워드는 Type casting 시에 사용되며 캐스팅하려는 변수가 null 또는 다른 타입인 경우 컴파일 오류는 발생하지 않지만 런타임시에 ClassCastException이 발생한다.
  • Safe casts (as?) : as? 키워드는 캐스팅하려는 변수가 null 또는 다른 타입인 경우 null이 반환되며 런타임 오류가 발생하지 않는다.
  • as, as? 키워드 관련 링크 : Kotlin 키워드 및 연산자 해부 Part 1의 1번, 2번 부분
  • 런타임 오류 관련 링크 : 런타임에러 vs 컴파일에러
val s:Int = 1
println(s as Int?)
  • 위와 같이 s에 대해 명시적으로 Non-nullable Int로 타입을 지정해주더라도 s as Int?는 런타임시에 null을 반환하지 않고 동일한 타입으로 인식한다. (Optional Class와 annotation 방식의 차이, Sub-typing과 관련하여 생각해볼 점)

5. Importance of nullability (2m)

  • Null은 reference type에 저장된 값이 없음을 표현하기 위해 필요한 개념이지만 런타임시에 예상치 못하게 NPE가 발생한다는 문제가 있어 anti-pattern 으로도 불린다. (관련 링크 : 안티패턴-위키피디아)
  • Kotlin은 Nullable/Non-nullable type, 몇 개의 Operator (?, ?:, !!, as?), smart casts 등 몇가지 기능을 통해 Nullability 문제를 효과적으로 다룰 수 있도록 설계 되었다. 이를 통해 null이 가지고 있던 문제를 효과적으로 통제하고 추적할 수 있도록 하였으며 (anti-pattern to legitimate pattern), Null을 First-class citizen으로 다룰 수 있도록 했다. (관련 링크 : 1급 객체(First-class citizen) 란? with Kotlin)
  • Java8에서 도입된 Optional 방식에 비해 Kotlin 방식이 좋은 점은 앞서 언급한 성능 오버헤드 이슈 외에도, 런타임시에 Nullable type과 Non-nullable type이 동일하게 동작하여 변수 할당에 있어 유연하다는 점이다. (관련 링크 : SubTyping-위키피디아)
  • 이와 같은 Kotlin의 Nullability를 다루는 방식이 효과적이고 유용한 것으로 실무 개발에서 검증되고 있다

--

--