반응형
타입 시스템
- 코틀린의 타입 시스템은 코드의 가독성을 향상시키는데 도움이되는 몇가지 특성을 새로 제공
널 가능성 (nullability)
- NullPointerException오류를 피할 수 있게 돕기 위한 코틀린 타입 시스템의 특성
- null에 대한 접근 방법을 실행시점 → 컴파일 시점
널이 될 수 있는 타입 (nullable type)
- 타입 시스템이 널이 될수 있는 타입을 명시적으로 지원
- 타입 이름 뒤에 물음표(?)를 명시
fun strLenSafe(s: String?) = ...
- null과 비교하고 나면 컴파일러는 그 사실을 기억하고, 해당 값을 널이 될 수 없는 타입의 값처럼 사용할 수 있다.
fun strLenSafe(s: String?) : Int =
if(s != null) s.length else 0
자바에서 NullPointerException 오류를 다루는 방법
- 애노테이션을 통해 @Nullable 이나 @NotNull 널 여부 표시를 할 수 있다.
- 자바 컴파일 절차가 아니기 때문에 일관성 적용 보장 X
- 모든 코드에 추가하는 일도 쉽지 않다.
- 자바8에 도입된 Optional 타입 등 null을 감싸는 래퍼 타입 활용
- 코드가 더 지저분해지고 실행 시점에 성능이 저하된다.
종합적인 해법 → 코틀린의 널이 될 수 있는 타입
안전한 호출 연산자 (?.)
- ?. 연산자는 null검사와 메서드 호출을 한번에 연산으로 수행
if (s != null) s.toUpperCase() else null
==
s?.toUpperCase()
- 호출하려는 값이 null이면 호출은 무시되고 null이 결과 값이 된다.
- 결과 타입은 String?이다.
- 메서드 호출뿐만 아니라 프로퍼티를 읽고 쓸때도 사용 가능.
val contry = this.company?.address?.country <- 연쇄해 사용 가능
엘비스 연산자 (?:)
- null 대신 사용할 디폴트 값을 지정할 때 사용
fun strLenSafe(s: String?) : Int =
s?.length ?: 0
- 코틀린에서는 return이나 throw 등의 연산도 식이다.
- 엘비스 연산자의 우항에도 넣을 수 있다.
val address = person.company?.address
?: throw IllegalArgumentException("No address") <- 주소가 없으면 예외를 발생
with(address) { //address는 널이 아니다.
}
안전한 캐스트 (as?)
- 값을 대상 타입으로 변환할 수 없으면 null을 반환
val otherPerson = o as? Person?: return false <- 타입이 일치하지 않으면 false 반환
return otherPerson.firstName == firstName &&
otherPerson.lastName == lastName
//안전한 캐스트를 하고 나면 Person타입으로 스마트 캐스트 된다.
널이 아님 (!!)
fun ignoreNulls(s: String?) {
val sNotNull: String = s!! <- 예외는 이 지점을 가리킨다.
println(sNotNull.length)
}
- s가 널이면 NPE발생
- 예외는 null값을 사용하는 코드가 아니라 단언문이 위치한 곳을 가리킨다.
!!를 널에 대해 사용해서 발생하는 예외 스택 트레이스에는 어떤 파일의 몇 번째 줄인지에 대한 정보는 들어있지만, 어떤 식에서 예외가 발생했는지에 대한 정보는 들어있지 않다.
여러 !! 단언문을 한줄에 함께 쓰지마라!
person.company!!.address!!.contry
let 함수
- 널이 될 수 있는 값을 널이 아닌 값만 인자로 받는 함수에 넘기는 경우
fun sendEmailTo(email: String) {..}
val email: String? = ...
sendEmailTo(email)
>>ERROR!!!
if (email != null) sendEmailTo(email)
- let함수는 자신의 수신 객체를 인자로 전달받은 람다에게 넘긴다.
- 널이 될 수 있는 값에 안전한 호출 구문(?.)을 사용해 람다에게 널이 될 수 없는 타입을 let에 전달한다.
email?.let {
email -> sendEmailTo(email)
}
여러 값이 널인지 검사해야 한다면 let호출을 중첩해서 처리할 수 있다. 하지만 코드가 복잡해져서 가독성 떨어짐.
그런경우 if를 사용해 모든값을 한꺼번에 검사하는 편이 낫다.
나중에 초기화할 프로퍼티
- 널이 될 수 없는 타입이라면 반드시 널이 아닌 값으로 프로퍼티를 초기화 해야한다.
- 널이 될 수 있는 타입을 사용해야한다.
- 널 검사나 !! 연산자를 써야한다.
- 이를 해결하기 위해 나중에 초기화(late-initialized) 할 수 있다.
- lateinit 변경자를 붙인다.
private lateinit var myService: MyService // 초기화 하지 않고 널이 될 수 없는 프로퍼티 선언
- 나중에 초기화 하는 프로퍼티는 항상 var이어야 한다.
- val 프로퍼티는 final 필드로 컴파일 되며, 생성자 안에서 반드시 초기화 해야 한다.
널이 될 수 있는 타입 확장
예) String을 확장해 정의된 isEmpty와 isBlank
@kotlin.internal.InlineOnly
public inline fun CharSequence.isEmpty(): Boolean = length == 0
public actual fun CharSequence.isBlank(): Boolean = length == 0 || indices.all { this[it].isWhitespace() }
- isNullOrEmpty 이나 isNullOrBlank메서드
- 안전한 호출을 하지 않아도 된다.
@kotlin.internal.InlineOnly
public inline fun CharSequence?.isNullOrEmpty(): Boolean {
contract {
returns(false) implies (this@isNullOrEmpty != null)
}
return this == null || this.length == 0
}
@kotlin.internal.InlineOnly
public inline fun CharSequence?.isNullOrBlank(): Boolean {
contract {
returns(false) implies (this@isNullOrBlank != null)
}
return this == null || this.isBlank() //두번째 this에는 스마트 캐스트가 적용된다.
}
- 코틀린에서는 널이 될 수 있는 타입의 확장 함수 안에서는 this가 널이 될 수 있다는 점이 자바와 다르다.
타입 파라미터의 널 가능성
- 타입 파라미터 T를 클래스나 함수 안에서 타입 이름으로 사용하면, 이름 끝에 물음표가 없더라도 T가 널이 될 수 있는 타입이다.
fun <T> printHashCode(t: T) {
println(t?.hashCode()) <- 't'가 null이 될 수 있으므로 안전한 호출을 써야한다.
}
//T의 타입은 Any?로 추론된다.
- 타입 파라미터가 널이 아님을 확실히 하려면 널이 될 수 없는 타입상한을 지정해야 한다.
fun <T: Any> printHashCode(t: T) //이제 T는 널이 될 수 없는 타입이다.
타입 파라미터는 널이 될 수 있는 타입을 표시하려면 반드시 물음표 타입 이름 뒤에 붙여야 한다는 규칙의 유일한 예외다.
플랫폼 타입
- 코틀린이 널 관련 정보를 알 수 없는 타입.
- 널 가능 or 널 불가능 아무 타입으로 처리 가능.
- 책임은 나에게 있다.
- 자바 타입은 코틀린에서 플랫폼 타입으로 표현 된다.
Person은 자바 클래스이다.
fun yellAt(person: Person) {
println(person.name.toUpperCase())
}
yellAt(Person(null))
- 코틀린 컴파일러는 공개(public) 가시성 코틀린 함수의 널이 아닌 타입인 파라미터와 수신객체에 대한 널 검사를 추가해준다. (= 컴파일만 통과 한다는 뜻인듯)
- 공개 가시성 함수에 널 값을 사용하면 즉시 예외 발생.
269p ??
- toUpperCase()가 수신객체로 널을 받을 수 없다는 예외가 발생한다고 하였는데 난 NPE발생함.

- 널 값을 처리해 줌으로써 예외 발생 X
fun yellAt(person: Person) {
println((person.name ?: "Anyone").toUpperCase())
}
코틀린이 왜 플랫폼 타입을 도입했는가? 모든 타입을 널이 될 수 있는 타입으로 다루면 널이 될 수 없는 값에 대해서도 불필요한 널 검사가 들어가기 때문. 검사에 드는 비용이 더 커진다.
val i: Int = person.name
ERROR: Type mismatch: inferred type is String! but Int was expected
- 코틀린 컴파일러가 표시한 String!라는 타입은 자바 코드에서 온 타입이다.
- ! 표기는 String! 타입의 널 가능성에 대해 아무 정보도 없다는 뜻
상속
- 코틀린에서 자바 메서드를 오버라이드 할 때, 반환 타입을 널이 가능한지 불가능한지 결정해야한다.
- 구현 메서드를 다른 코틀린 코드가 호출할 수 있으므로, 코틀린 컴파일러는 널이 될 수 없는 타입으로 선언한 모든 파라미터에 대해 널이 아님을 검사하는 단언문을 만들어 준다.
- 메서드에 널 값을 넘기면 에러 발생
interface StringProcessor {
void process(String value);
}
- 코틀린 컴파일러는 두 구현을 다 받아들인다.
override fun process(value: String) {
}
override fun process(value: String?) {
}
반응형
'코틀린 & 컴포즈 & Java > 코틀린인액션' 카테고리의 다른 글
Kotlin Plus 산술연산자 오버로딩 (0) | 2023.05.09 |
---|---|
코틀린 원시타입(Kotlin Primitive Type) (0) | 2023.04.06 |
[Kotlin] 스코프 펑션(Scoepe Function) (0) | 2023.03.23 |
[Kotlin] 람다에 대해 알아보자(2) (0) | 2023.03.23 |
[Kotlin] 람다에 대해 알아보자 (0) | 2023.03.23 |