728x90
반응형
널 가능성[1]의 내용과 이어집니다.
[Kotlin In Aciton] 6장. 코틀린 타입 시스템(1) - 널 가능성[1]
✅ 널 가능성 널 가능성(nullability)은 NullPointer Exception(NPE)를 피할 수 있게 돕기 위한 코틀린 타입 시스템의 특성이다. 코틀린을 비롯한 최신 언어에서 null에 대한 접근 방법은 가능한 한 이 문제를
dahoonkk.tistory.com
📌 let 함수
- let 함수를 사용하면 널이 될 수 있는 식을 더 쉽게 다룰 수 있다.
- let 함수를 안전한 호출 연산자(?.)와 함께 사용하는 경우 식을 평가해서 결과가 널인지 검사한 다음 그 결과를 변수에 넣는 작업을 간단한 식을 통해 한꺼번에 처리할 수 있다.
- 가장 흔한 용례 : 널이 될 수 있는 값을 널이 아닌 값만 인자로 받는 함수에 넘기는 경우
/* let을 사용해 null이 아닌 인자로 함수 호출하기 */
fun sendEmailTo(email: String) {
println("Sending email to $email")
}
fun main() {
var email: String? = "yole@example.com"
email?.let{ sendEmailTo(it) } // Sending email to yole@example.com
email = null
email?.let{ sendEmailTo(it) } // return nothing
- 위의 예제와 같이 let을 통해 nul이 아닌 경우 람다로 값을 넘겨 함수를 실행하게 된다.
- 하지만 아무 값도 넘기지 않으면 return 값은 없다.
📌 나중에 초기화할 프로퍼티
- 코틀린에서는 lateinit 변경자를 통해 프로퍼티를 나중에 초기화 할 수 있다.
- 나중에 초기화하는 프로퍼티는 항상 var여야 한다.
- 그렇지만 나중에 초기화하는 프로퍼티는 널이 될 수 없는 타입이라 해도 더 이상 생성자 안에서 초기화할 필요가 없다.
- 그 프로퍼티를 초기화하기 전에 프로퍼티에ㅔ 접근하면 "lateinit property - has not been initialized" 예외가 발생한다.
- 이는 예외가 어디서 발생했는지 확인이 가능하기 때문에 단순한 NPE가 발생했을 때보다 훨씬 좋다.
/* 나중에 초기화하는 프로퍼티 사용하기 */
class MyService {
fun performAction() : String = "foo"
}
class MyTest {
private lateinit var myService: MyService // 초기화하지 않고 널이 될 수 없는 프로퍼티를 선언한다.
@Before fun setUp() {
myService = MyService() // setUp 메서드를 통해 프로퍼티를 초기화
}
@Test fun testAction() {
Assert.assertEquals("foo", myService.performAction()). // 널 검사를 수행하지 않고 프로퍼티를 사용한다.
}
}
📌 널이 될 수 있는 타입 확장
- 널이 될 수 있는 타입에 대한 확장 함수를 정의하면 null 값을 다루는 강력한 도구로 활용할 수 있다.
- 어떤 메서드를 호출하기 전에 수신 객체 역할을 하는 변수가 널이 될 수 없다고 보장하는 대신, 직접 변수에 대해 메서드를 호출해도 확장 함수인 메서드가 알아서 널을 처리해준다.
- 예를 들어 코틀린 라이브러리에서 String을 확장해 정의된 isNullOrEmpty와 isNullOrBlank라는 함수를 생각해보자.
/* null이 될 수 있는 수신 객체에 대해 확장 함수 호출하기 */
fun verifyUserInput(input: String?) {
if(input.isNullOrBlank()) { // 안전한 호출을 하지 않아도 됨
println("Please fill in the required fields")
}
}
- 위의 예제는 null을 수신 객체로 전달해도 아무런 예외가 발생하지 않는다.
- 안전한 호출 없이도 널이 될 수 있는 수신 객체 타입에 대해 선언된 확장 함수를 호출 가능하다.
- 함수는 null 값이 들어와도 이를 적절히 처리한다.
📌 타입 파라미터의 널 가능성
- 코틀린에서는 함수나 클래스의 모든 타입 파라미터는 기본적으로 널이 될 수 있다.
- 널이 될 수 있는 타입을 포함하는 어떤 타입이라도 타입 파라미터를 대신할 수 있다.
- 따라서 타입 파라미터 T를 클래스나 함수 안에서 타입 이름으로 사용하면 이름 끝에 물음표가 없더라도 T가 널이 될 수 있는 타입이다.
/* 널이 될 수 있는 타입 파라미터 다루기 */
fun <T> printHashCode(t: T) {
println(t?.hashCode()) // "t"가 null이 될 수 있으므로 안전한 호출을 써야만 한다.
}
printHashCode(null) // "T"의 타입은 "Any?"로 추론된다.
// null
- 이 예제에서 printHashCode 호출에서 타입 파라미터 T에 대해 추론한 타입은 널이 될 수 있는 Any? 타입이다.
- t 파라미터의 타입 이름 T에는 물음표가 붙지 않았지만 t는 null을 받을 수 있다.
- 타입 파라미터가 널이 아님을 확실히 하려면 널이 될 수 없는 타입 상한(Upper Bound)을 지정해야 한다.
- 이렇게 널이 될 수 없는 타입 상한을 지정하면 널이 될 수 있는 값을 거부하게 된다.
/* 타입 파라미터에 대해 널이 될 수 없는 상한을 사용하기 */
fun <T: Any> printHashCode(t: T) { // 이제 "T"는 널이 될 수 없는 타입이다.
println(t.hashCode())
}
printHashCode(null) // 이 코드는 컴파일되지 않는다. 널이 될 수 없는 타입의 파라미터에 널을 넘길 수 없다.
// Error: Type parameter bound for 'T' is not satisfied
printHashCode(42)
// 42
📌 널 가능성과 자바
- 자바의 @Nullable String은 코틀린 쪽에서 볼 때 String?와 같고, 자바의 @NotNull String은 코틀린 쪽에서 String과 같다.
- 코틀린은 여러 널 가능성 애노테이션을 알아본다.
- JSR-305표준(javax.annotation package), 안드로이드, 젯브레인스 도구들이 지원하는 애노테이션
- 이런 널 가능성 애노테이션이 소스코드에 없는 경우 자바의 타입은 코틀린의 플랫폼 타입이 된다.
- JSR-305표준(javax.annotation package), 안드로이드, 젯브레인스 도구들이 지원하는 애노테이션
❗️ 플랫폼 타입
- 플랫폼 타입은 코틀린이 널 관련 정보를 알 수 없는 타입을 말한다.
- 코틀린이 따로 플랫폼 타입을 선언할 수 없으며 자바에서 가져온 타입이 플랫폼 타입이 된다.
- 컴파일러는 모든 연산을 허용하며, 플랫폼 타입의 사용에 대한 모든 책임을 개발자에게 돌린다.
- 그렇다면 코틀린은 이러한 플랫폼 타입을 왜 도입하였을까?
- 모든 자바 타입을 널이 될 수 있는 타입으로 다루면 더 안전하다 생각할 수도 있을 것이다.
- 물론 그런 식으로 사용해도 되지만 모든 타입을 널이 될 수 있는 타입으로 다루게 되면 널이 될 수 없는 값에 대해서도 불필요한 널 검사가 들어가게 될 것이다.
- 특히 제네릭을 다룰 때 더 문제가 생긴다.
- 예를 들어 모든 자바 제네릭에 대해 코틀린에서와 같이 Nullable하게 다루면 제네릭 안의 원소에 접근할 때마다 널 검사를 수행하거나 안전한 캐스트를 수행해야 한다.
- 이런 식으로 처리할 경우 널 안전성으로 얻는 이익보다 검사에 드는 비용이 훨씬 더 커지게 된다.
- 따라서 코틀린에서는 자바의 타입을 가져온 경우 프로그래머에게 그 타입을 제대로 처리할 책임을 부여하는 접근 방법을 택한 것이다.
❗️ 상속
- 코틀린에서 자바 메소드를 오버라이드할 때 그 메소드의 파라미터와 반환 타입을 널이 될 수 있는 타입으로 선언할지 널이 될 수 없는 타입으로 선언할지 결정해야 한다.
/* String 파라미터가 있는 자바 인터페이스 */
/* 자바 */
interface StringProcessor {
void process(String value);
}
/* 자바 인터페이스를 여러 다른 널 가능성으로 구현하기 */
/* 코틀린 */
class StringPrinter : StringProcessor {
override fun process(value: String) {
println(value)
}
}
class NullableStringPrinter : StringProcessor {
override fun process(value: String) {
if(value != null) {
println(value)
}
}
}
- 구현 메소드를 다른 코틀린 코드가 호출할 수 있으므로 코틀린 컴파일러는 널이 될 수 없는 타입으로 선언한 모든 파라미터에 대해 널이 아님을 검사하는 단언문을 만들어준다.
- 따라서 위의 예제 처럼 자바 클래스나 인터페이스를 코틀린에서 구현할 경우 널 가능성을 제대로 처리해줘야 한다.
728x90
반응형
'Studying > Kotlin' 카테고리의 다른 글
[Kotlin In Action] 6장. 코틀린 타입 시스템(4) - 컬렉션과 배열 (0) | 2023.06.01 |
---|---|
[Kotlin In Action] 6장. 코틀린 타입 시스템(3) - 코틀린의 원시 타입 (0) | 2023.05.31 |
[Kotlin In Aciton] 6장. 코틀린 타입 시스템(1) - 널 가능성[1] (0) | 2023.05.25 |
[Kotlin In Action] 5장. 람다로 프로그래밍(5) - 수신 객체 지정 람다 : with와 apply (0) | 2023.05.23 |
[Kotlin In Action] 5장. 람다로 프로그래밍(4) - 자바 함수형 인터페이스 활용 (0) | 2023.05.23 |