231221 TIL #274 Kotlin #9 타입 시스템

김춘복·2023년 12월 21일
0

TIL : Today I Learned

목록 보기
274/543
post-custom-banner

Today I Learned

Kotlin in Acion 이어서 타입 공부!


코틀린 타입 시스템

  • Nullability
    NullPointException(NPE)를 피하게 해주는 코틀린 타입 시스템의 특성.

Null이 될 수 있는 타입(Nullable)

코틀린과 자바의 가장 중요한 차이는 코틀린에선 널이 될 수 있는 타입을 명시적으로 지원한다는 점이다.

  • Type? = Type or null
    Nullable 어떤 타입이든 타입 이름 뒤에 ?를 붙이면 그 타입의 변수나 프로퍼티에 null 참조를 저장할 수 있다.
    즉, 물음표가 없는 타입은 기본적으로 null이 될 수 없다.

  • 널이 될 수 있는 타입의 변수가 있으면 그에 대해 수행할 수 있는 연산이 제한된다. ?가 달린 타입의 변수에 대해 변수.메서드()처럼 직접 호출할 수 없다. 직접 호출 시 컴파일러가 컴파일 과정에서 에러를 뱉는다.

  • 단, null과의 비교를 하고 나면 컴파일러가 그것을 기억해 원래처럼 사용할 수 있다.

fun strLenSafe(s: String?): Int = if(s!=null) s.length else 0

안전 호출 연산자 ?.

?.를 사용하면 해당 변수나 객체가 null이 아닌 경우에만 접근하고 그렇지 않은 경우에는 null을 반환한다.

  • s?.toUpperCase()는 if(s!=null) s.toUpperCase() else null와 같다.
    변수, 프로퍼티, 메소드 등에 접근 시 null 검사를 알아서 해주는 연산자이다.
fun printAllCaps(s: String?) {
    val allCaps: String? = s?.toUpperCase()
    println(allCaps)
}

fun main(args: Array) {
    printAllCaps("abc")	// ABC
    printAllCaps(null)	// null
}

엘비스 연산자 ?:

null 일 때 디폴트 값을 정의하는 데 사용된다.
변수나 표현식이 null이 아닌 경우에는 해당 값을 사용하고, null인 경우에는 우측에 있는 값으로 대체된다.

 fun getName(str: String?) {
    val name = str ?: "Unknown"
}
// 파라미터로 들어오는 인자가 null이면 "Unknown"으로 저장
  • 코틀린에선 return이나 throw도 연산도 이다.
    따라서 엘비스 연산자 우항에 아래처럼 return, throw 등의 연산을 넣을 수 있다.
    address?: throw RuntimeException("No Address")

안전한 캐스트 as?

as?는 안전한 형 변환을 수행하는 데 사용된다. 형 변환할 수 있는지 불확실한 상황에서 사용된다.
이 연산자를 사용하면 형 변환이 실패할 경우 예외가 발생하는 대신 null을 반환한다.

  • as를 사용할 때 마다 is로 형변환이 가능한지 검사해 보지 않아도 된다.
val x: String? = "Hello"
val y: Int? = x as? Int
// x가 nullable하게 선언되어 있으므로 Int?로 형변환 가능

not-null assertion !!

!!는 변수나 표현식이 null이 아님이 분명할 때 사용되며, 해당 값이 null일 경우 예외를 발생시킨다.
null이 발생하면 오히려 NPE를 던진다.

  • 아래는 nullable한 인자를 null이 될 수 없는 타입으로 변환한 예이다.
fun ignoreNulls(s: String) {
	val sNotNull: String = s!!
    println(sNotNull.length)
}
  • 사용 시 어떤 값이 null인지 확실히 하기 위해 한줄에 여러번 !!를 쓰는 것은 권장되지 않는다.

let 함수

Kotlin 표준 라이브러리에서 제공하는 함수 중 하나로, 주어진 블록을 수신 객체로 호출한다.
일반적으로 let은 null이 아닌 객체에서 작업을 수행하거나, 어떤 값을 다른 형태로 변환할 때 사용된다.

  • 기본 구조
    T : 수신객체 / R : 람다 블록의 결과 타입 / block : 수신객체에 대해 수행할 람다 식
inline fun <T, R> T.let(block: (T) -> R): R
  • 예시
val nullableString: String? = "Hello, Kotlin"

nullableString?.let { nonNullString ->
    println(nonNullString.length) // 수신객체가 null이 아닌 경우에만 실행된다. null이면 아무 작업도 안함
}

지연 초기화 프로퍼티 lateinit

lateinit은 변수를 선언 시 바로 초기화 하지 않고, 나중에 필요한 시점에 초기화하도록 도와준다.
DI를 쓰는 프레임워크에서 자주 사용한다.

class Example {
    lateinit var name: String
}
  • val은 당연히 사용 불가하고 var에서만 사용 가능하다.

  • 정수형(Int, Long, Short, Byte), 실수형(Double, Float), 불린(Boolean) 타입에는 사용할 수 없다.

  • 초기화 전에 사용시 NPE가 발생하는게 아니라, 'UninitializedPropertyAccessException'가 발생한다.
    프로퍼티가 선언된 시점에서 not null로 간주되기 때문에 nullable이 아니다.

  • 초기화는 단 한번만 가능하다.


타입 파라미터 T

코틀린에서 함수나 클래스의 모든 타입 파라미터(T)는 기본적으로 null이 될 수 있다.
그래서 T를 크랠스나 함수 안에서 타입이름으로 사용하면 ?가 없어도 nullable한 타입이다.
T는 Any?타입으로 추론되므로 안전한 호출(?.)을 사용해야 한다.

  • 만약 not-null을 확실히 하려면 타입상한(upper bound)를 지정해야 한다.
    <T>를 <T:Any>로
fun <T> printHashCode(t:T) {
	println(t?.hashCode())
}

fun <T:Any> printHashCodeNotNull(t:T) {
	println(t?.hashCode())
}

플랫폼 타입

플랫폼 타입은 코틀린이 null 관련 정보를 알 수 없는 타입이다.
이 타입은 nullable로 처리해도 되고, not-null 타입으로 처리해도 되지만 책임은 개발자가 진다.

  • 자바코드를 코틀린에서 다루게 될 경우 자바에선 type의 null 가능성에 대한 명시가 없는 경우가 있다.(@NotNull 같은 애너테이션 제외) 이때 자바의 type은 플랫폼 타입 취급을 받느다.

  • 자바의 Type은 코틀린에서 Type이든 Type?로 처리하든 개발자가 선택하기에 따라 달려있다.

  • 모든 타입을 nullable로 처리하면 검사에 시간과 자원이 소모된다. 특히 자바에서 ArrayList<String>을 ArrayList<String?>처럼 코틀린에서 다루면 매 원소마다 널 검사를 수행해서 검사에 드는 비용이 커진다.
    그러므로 자바API를 다룰 땐 NPE가능성과 성능을 모두 염두해 두고 null 처리를 해야 한다.

profile
Backend Dev / Data Engineer
post-custom-banner

0개의 댓글