스터디파이 코틀린 입문 스터디 (https://studypie.co/ko/course/kotlin_beginner) 관련 자료입니다.
코틀린 입문반은 Kotlin을 직접 개발한 개발자가 진행하는 Coursera 강좌인 “Kotlin for Java Developers” (https://www.coursera.org/learn/kotlin-for-java-developers) 를 기반으로 진행되며 아래는 본 강좌 요약 및 관련 추가 자료 정리입니다.
목차
(9) 실습 : Mastermind in a functional style, Nice String, Taxi Park
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처럼
int
와Integer
를 구분하지 않으며 오직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를 다루는 방식이 효과적이고 유용한 것으로 실무 개발에서 검증되고 있다