Null
null
null
이 될 수 있는 타입 뒤에는 ?
를 붙이면 null
참조를 저장할 수 있다.
?.
Kotlin
에서 제공하는 안전한 호출 연산자이다.
일반적인 Null
체크는 if
문을 통해 체크할 수 있지만, ?.
를 이용한다면, Null
검사와 메서드 호출을 동시에 할 수 있다.
/// 두 코드는 동일하다.
s?.toUpperCase()
if(s!=null) s.toUpperCase() else null
또한 null
이 될 수 있는 프로퍼티
접근 시 안전한 호출도 가능하다.
?:
?:
는 null
대신 사용할 디폴트 값을 지정할 때 편리하게 사용할 수 있다.
사용 방법은 다음과 같다.
fun foo(s : String?){
val t : String = s ?: ""
}
s
가 null
이라면, t
에 ""
을 저장한다는 의미이다.
Kotlin
에서는return
과throw
도식
이기 때문에, 엘비스 연산자 우항에 해당 식을 넣어줄 수도 있다.
안전한 캐스트 as?
kotlin
에서 타입 캐스팅을 하는 방법은 as
를 이용하는 것이다.
만약, as
를 이용한 타입 캐스팅시 지정한 타입으로 바꿀 수 없는 경우라면, ClassCastException
을 반환하게 된다.
이를 방지하기 위해 is
를 이용해 미리 변환 가능한지 체크할 수 있지만, 매 방법마다 is
를 통해 체크하는 것은 번거로울 수 있다.
이를 해결하기 위해 as?
를 사용한다.as?
는 어떤 값을 지정한 타입으로 캐스팅한다. as?
는 대상 타입으로 변환할 수 없다면, null
을 반환한다.
널이 아님을 선언 !!
!!
은 어떤 값이든 null
이 될 수 없는 타입으로 강제로 바꿀 수 있다.
만약, null
이 될 수 있는 값에 !!
를 사용하게 되면, NPE
가 발생한다.
!!
은 여러 단언문을 한 줄에 쓰는 것을 피하는 것이 좋다.
let
let
을 이용하면, null
이 될 수 있는 식을 더 잘 처리할 수 있다.
만약에 어떤 값이 null
이 아니라면, 어떠한 작업을 수행한다고 가정하면,값?.let{}
다음과 같이 작성하여 null
을 체크해주면 된다.
let
은 자신의 수신 객체를 lambda
에 넘기므로, 안전한 호출을 사용할 수 있게 해준다.
예를들어 email
의 주소값이 null
이 아니면 mail
을 보내도록 하는 코드를 작성하면,
email?.let{sendEmail(it)}
다음과 같이 작성할 수 있다.
꼭
let
을 이용한 체크를 해야만 하는 걸까?정답은 아니다! 복잡한 로직이나 여러 조건을 동시에 확인해야 하는 경우에는
if
를 사용하는 것이 더 좋다
lateinit
lateinit
은 나중에 초기화 한다는 뜻이다.
lateinit
의 프로퍼티는 반드시 var
이어야 한다. val
은 final
필드이므로, 컴파일 단계에서 반드시 초기화되야 한다. 그렇기에 나중에 초기화하는 프로미터는 var
을 사용해야 한다.
DI
와 함께 사용하는 경우가 많다.Hilt
를 사용할 때,@Inject
과 함께 의존성을 주입할 때lateinit
으로 작성한다.
나중에 선언하는 것처럼 선언되어 있지만,Hilt
에 의해 의존성이 주입된다.
타입 파라미터의 Null 가능성
kotlin
에서 타입 파라미터 T
를 클래스나 함수 안에서 타입 이름으로 사용하면 ?
가 없더라도 null
이 될 수 있는 타입이다.
null
이 아님을 확실히 하고 싶다면?
타입 상한 을 해야한다!
타입 상한
타입 상한이란, 특정 타입이나, 서브 타입만을 허용하도록 하는 기능이다.
예를 들어
class Box<T: Number>(val value: T)
위의 코드처럼 T
를 Number
로 제한해서 Number
나 서브 타입
만이 허용되도록 제한 하는 것이다. Number
의 서브타입은, - Int
Double
Float
Long
Short
Byte
가 있으므로, 해당 서브 타입
만 허용하도록 한다.
val intBox: Box<Int> = Box(10) // 정상 작동
val doubleBox: Box<Double> = Box(10.5) // 정상 작동
val stringBox: Box<String> = Box("Hello") // 컴파일 에러
3개의 상황을 예시로 들면, Int
와 Double
의 경우에는 서브 타입
이므로 문제가 없다.
하지만, String
의 경우에는 서브 타입
이 아니므로, 컴파일 에러가 발생한다.
그렇다면, 왜 타입 상한을 쓰는 것일까?
장점은 다음과 같다.(with. GPT4)
타입 안전성(Type Safety): 제네릭 타입의 범위를 제한함으로써, 런타임에 발생할 수 있는 타입 관련 오류를 컴파일 시점에 잡아낼 수 있다.
코드의 명확성: 타입 상한을 사용함으로써 개발자는 해당 제네릭 클래스나 함수가 어떤 타입의 값을 받을 수 있는지 명시적으로 표현할 수 있으며, 이는 코드의 가독성과 유지보수성을 높여준다.
추가 기능의 사용: 타입 상한을 통해 특정 타입의 메서드나 프로퍼티에 접근할 수 있게 된다. 예를 들어,
T
가Number
타입으로 제한되어 있을 때,T
타입의 변수에서Number
클래스의 메서드와 프로퍼티를 사용할 수 있다.
코틀린의 원시타입
java
에서의 원시타입과 참조타입에 대해 설명해보겠다.
원시타입
- 원시타입은
Int
,Boolean
등이 있다. - 원시타입은 메모리에 값이 직접 저장이 된다.
참조 타입
- 참조타입은 메모리상에 객체의 주소를 저장한다.
두 특징을 경험과 함께 생각해보면, 코딩을 배울 때 이런 경험을 해본적이 다들 있을 것 같다.
예를 들어 원시타입의 Int
는 값을 다른 변수에 할당해도 서로에게 영향을 주지 않는다. 하지만 참조 타입을 변수에 할당 하고 할당한 변수값을 수정하게 되면 두 변수 모두 수정된 경험이 있을 것이다.
참조 타입은 주소를 저장하기 때문에, 변수에 할당하면 주소값이 할당되기 때문이다.
java
에서는 컬렉션에 원시타입을 담을 수 없다.Collection<Int>
는 불가능하다. java
는 참조타입이 필요한 경우에 특별한 래퍼타입으로 원시 타입을 감싸야 한다.
그렇기에 Collection<Integer>
를 사용해야 한다.
하지만, Kotlin
에서는 원시 타입과 래퍼 타입을 구분하지 않는다. 그렇기에 항상 같은 타입을 사용한다.
그렇다면 코틀린에서는 모든 타입을 객체로 표현하는 것인가?
정답은 아니다. 코틀린은 실행 시점에서 가장 효율적인 방식으로 표현한다.
Int
의 경우에는 Null
이 될 수 없다, 하지만 Kotlin
에서는 Int
도 Null
로 사용할 수 있다, 코틀린에서는 Int
로 저장하는 것이 아닌, Integer
로 효율적으로 저장하기 때문에 가능하다.
숫자 변환
Kotlin
에서는 숫자를 변환할 때, 다른 타입의 숫자를 다른 타입에 저장할 때 자동으로 타입이 변환되지 않는다.
직접 변환 메서드를 호출해야 가능하다.
Any, Any? : 최상위 타입
java
에서는 object
가 최상위 타입이지만, kotlin
에서는 Any
타입이 모든 널이 될 수 없는 타입의 조상이다.
java
에서는 원시타입은 object
를 정점으로하는 계층에 없다. 그렇기에, 사용하려면 integer
와 같은 래퍼타입으로 감싸주어야 가능하다.
Any
는 null
이 올 수 없다. 그렇기에 null
을 허용하려면 Any?
를 사용해야한다.
'안드로이드 > Action Kotlin' 카테고리의 다른 글
Action Kotlin 6 - 연산자 오버로딩과 기타 관계 (0) | 2023.10.30 |
---|---|
Action Kotlin 4 - 람다로 프로그래밍 (0) | 2023.10.25 |
Action Kotlin 3 - 클래스, 객체, 인터페이스 (1) | 2023.10.24 |
Action Kotlin 2 - 함수의 정의와 호출 (0) | 2023.10.24 |
Action Kotlin - 1 (0) | 2023.10.24 |