[Kotlin Tutorial] Kotlin 의 Type system - Chap6. The Kotlin type system |
출처 : Kotlin in action
6.1. Nullability
6.1.1. Nullable types
-
Kotlin 은 nullable types 를 지원한다.
nullable type 이라는 것은 어떤 variable 이 null 을 가질 수 있는지를 명시하는 것이다.
-
nullable 하지 않은 곳에 null 을 넣으면 compile error 가 난다.
기본 type 은 nullable 하지 않으며, nullable 을 만드려면 type 뒤에 ? 를 붙여주면 된다.
어떤 타입이든 뒤에 ? 를 붙여줄 수 있다.
fun strLenSafe(s: String?) = …
6.1.2. The meaning of types
-
@Nullable, @NotNull annotation 이 java 에서 지원되고 IDE 가 NPE 를 찾아내는 데 도움이 되지만,
모든 code base 에 이 규칙을 지키는 것은 어려우며( 특히 3rd lib 을 쓰는 경우는 더 ), compile process 에 이 annotation 처리는 처리되지 않는다.
그래서 쉽게 NPE 를 만날 수 있다.
-
Java8 에서는 Optional 이 등장하긴 했지만, 객체를 만들기 때문에 Runtime 에 문제가 될 수도 있고,
verbose 코드들이 증가하며, 관리하는 것이 쉽지 않고, 마찬가지로 3rd lib 과 동작할 때는 여튼 null 처리를 해야 한다.
-
nullable type 은 compile time 에만 valid 하다 ( Runtime 에는 invalid )
6.1.3. Safe call operator: “?.”
-
?. 은 null check 와 method call 을 함께 한다.
// Kotlin
str?.toUpperCase() // 이 녀석의 값 assign 하는 경우에는 해당 변수는 nullable 이어야 한다
// Java
if( s != null )
s.toUpperCase()
6.1.4. Elvis operator: “?:”
-
null 대신 default value 를 제공하는 operator 가 있으니 이것을 Elvis operator 라고 한다. ( null-coalescing operator 라고도 함 )
fun foo(s: String?){
val t: String = s ?: “"
}
-
Elvis operator 는 function 의 precondition 을 확인할 때 사용하면 좋다.
fun printShippingLabel(person: Person){
val address = person.comany?.address ?: throw IllegalArgumentException(“No address”)
...
}
6.1.5. Safe casts: “as?”
-
Casting 하려는 type 이 맞으면 casting, 아니면 null 을 assign 한다.
Elvis operator 와 함께 equals 같은 데서 쉽게 사용될 수 있다.
override fun equals(o: Any?): Boolean{
val otherPerson = o as? Person ?: return false;
}
6.1.6. Not-null assertions: “!!”
-
!! 를 사용하면 non-null type 으로 변경하거나 exception 을 던진다.
fun ignoreNulls(s: String?){
val sNotNull: String = s!!
println(sNotNull.length)
}
-
!! 를 쓸 때는 same line 에 concat 해서 쓰는 것은 피해야 한다. stack trace 가 line # 로 나오기 때문이다.
person.company!!.address!!.country // 어떤 녀석이 null 인지 stack trace 로 찾기 어렵다
6.1.7. The “let” function
-
let 은 non-null param 을 받는 function 을 nullable argument 로 call 해야 할 때 사용될 수 있다.
fun sendEmailTo(email: String){ … }
val email: String? = …
sendEmailTo(email) // compile error 가 난다
// if(email != null) 후 호출하면 compile error 를 막을 수 있다.
let 은 호출부를 lambda 안에 넣는다. 이 때 lambda 의 param 은 nonnull 이 보장된다
email?.let { email -> sendEmailTo(email) } // it 으로 받을 수도 있다.
// email?.let { sendEmailTo(it) }
// ? 를 안 붙이고 let 을 호출하면 이 때는 it 이 null 일 수 있다.
6.1.8. Late-initialized properties
-
class MyService{
fun performAction: String = “foo"
}
class MyTest{
// android Activity 의 onCreate 나 Test의 setUp 케이스에 실제 null 이 아니게 되는데 constructor 가 아니라 nullable type 으로 지정해야 한다...
private var myService: MyService? = null
@Before fun setUp(){
myService = MyService()
}
@Test fun testAction(){
Assert.assertEquals(“foo”, myService!!.performAction()) // nullable type 이라 myService 뒤에 !! 가 붙는다
}
}
lateinit 키워드를 써주면 해결된다.
private lateinit var myService: MyService
lateinit 을 써줬어도 여전히 var 를 써야 하긴 하지만, non-null type 임에도 var 를 constructor 에서 initialize 해주지 않아도 compile error 가 나지 않는다
만약 init 전에 접근하면 “lateinit property myService has not been initialized” 라는 runtime error 가 난다.
-
lateinit 은 dependency injection framework 에 유용하게 쓰인다.
6.1.9. Extensions for nullable types
-
nullable type 에 extension function 을 정의하는 것은 null 을 다루는 아주 좋은 방법이다.
아래와 같이 ?. 와 함께 써주면 String 과 String? 모두에 적용할 수 있는 extension function 이 된다.
fun String?.isNullOrBlank(): Boolean = this == null || this.isBlank()
// 여기서는 nullable type 에 대해서 호출할 수 있으므로 this 는 null 일 수 있어 꼭 체크해주어야 한다
-
extension function 을 정의할 때는 nullable type 에 정의할 것인지 아닌지 고민해봐야 한다.
기본적으로는 nullable type 에 하는 것이 좋다.
6.1.10. Nullability of type parameters
-
기본적으로 모든 type parameter( generic 의 T 같은 녀석들) 는 ? 이 붙지 않아도 nullable 이다.
nullability 에 대한 유일한 예외적인 케이스이다.
fun <T> printHashCode(t: T){ // T 는 Any? 로 추정된다
println(t?.hashCode())
}
-
type parameter 를 nonnull 로 만드려면 upper bound 를 세팅해줘야 한다.
fun <T: Any> printHashCode(t : T){
..
}
-
generic 에 대해서는 나중에 더 세밀하게 배운다.
6.1.11. Nullability and Java
-
Kotlin 에서 Java 코드를 이용할 때는 @nullable, @NonNull annotation 을 참조한다.
그래서 예를 들어 Java 의 @nullable String 의 경우 String? 으로 인식한다.
이 때 nullability annotation 은 여러 가지 version 을 지원한다.
javax.annotation, android.support.annotation, org.jetbrains.annotations
-
annotation 이 없을 때는 Kotlin 의 “platform type” 이라는 것이 된다.
Platform type 이란 nullbility 정보가 없는 type 을 이야기한다.
Platform type 은 Type? 으로도 Type 으로도 작동한다. 즉 호출자에게 모든 것이 달려있다. ( 엄밀히 얘기하면 nullble 이지만... )
여튼 platform type 을 사용할 때에는 NPE 발생 가능성이 높아진다.
-
Kotlin function 에 대해 compiler 는 function call 의 receiver, parameter 에 대해 체크하는 코드를 생성한다.
( 직접 해당 variable 들이 사용될 때가 아니다. )
그래서 해당 param 이 사용될 때까지 잘못된 코드가 수행되는 것을 막아준다.
-
Platform type 을 사용할 때에는 항상 document 를 확인하거나 내부 코드를 보고 nullable 인지 nonnull 인지 확인해야 한다.
전부 nullable 로 만들어 버리면 쓸데없이 null check 를 해야 하는 코드들이 들어갈 수 있다. ( 실제 동작은 nonnull 인데도 )
그래서 Platform type 이란 개념을 만들면서 programmer 에게 자유도를 주기로 했단다.
-
Platform type 은 ! 가 붙는다.
! 는 Error message 에서만 볼 수 있고, 코드에서 이 타입을 직접 쓸 순 없다.
>>> val i : Int = person.name
ERROR: Type mismatch: inferred type is String! but Int was expected
Platoform type 은 아래와 같이 두 가지 형태의 type 에 둘다 대입 가능하다.
>>> val s : String? = person.name
>>> val s : String = person.name
-
Java 의 것을 override 할 때는 nullability 는 어떨까?
/* Java */
interface StringProcessor{
void process(String value );
}
아래와 같이 nullable 과 nonnull 모두 가능하다
class StringPrinter: StringProcessor{
override fun process(value: String){
println(value)
}
}
class NullableStringPrinter: StringProcessor{
override fun process(value: String?){
value?.let{ println(it) }
}
}
댓글