안전 호출(safe call)과 스마트 타입 변환에 관한 보충 설명입니다. 자세한 내용은 쉽게 다가가는 최신 프로그래밍: 코틀린 - 1.13 안전 호출을 참고하기 바랍니다. 아래 예제에서 함수 isSmallCase()는 문자열이 모두 소문자로 이루어져 있는지 조사합니다. 4개의 다른 문자열을 사용해 이 함수가 제대로 동작하는지 테스트합니다.
문자열 ""를 빈 문자열(empty string)이라고 부릅니다. 빈 문자열은 원소가 없기 때문에 for 루프를 실행하지 못합니다. 빈 문자열의 isSmallCase() 실행 결과는 true입니다. 문자열 " "은 공백 문자(space) 1개를 갖고 있지만 소문자가 아니어서, isSmallCase() 실행 결과 false를 반환합니다.
fun isSmallCase(s: String): Boolean {
for (ch in s) {
if (!ch.isLowerCase( )) return false
}
return true
}
fun main() {
println(isSmallCase("Kotlin")) // 첫 번째 문자가 대문자. false
println(isSmallCase("")) // 빈 문자열(=empty string). true
println(isSmallCase(" ")) // 공백 문자 1개를 갖는 문자열. false
println(isSmallCase("kotlin")) // true
}
■ 널 또는 널 타입 변수를 형식인자로 전달하면?
그런데, isSmallCase()에 널(null)을 전달하면 어떻게 될까요? 아래처럼 isSmallCase()는 널이 아닌 String 타입(non-null type String)만을 전달받을 수 있다는 에러 메시지가 출력됩니다.
isSmallCase()는 널이 아닌 문자열만을 전달받을 수 있습니다. 널 타입 변수 foo를 선언하고, foo를 isSmallCase()에 전달하면 타입이 일치하지 않는다는 메시지가 출력됩니다.
■ 널(null)이 뭔가요
널은 변수가 갖는 값을 구체적으로 할당하지 않았다는 의미입니다. 변수가 Int 타입이면, 정수 값을 저장하기 위한 메모리 공간을 할당합니다. 널은 (제대로 관리되고 있지 않은 임의의) 메모리 공간을 가리킵니다. 제주도에 여행 가기 위해 호텔을 예약했다고 합시다. 체크인 하기 전까지는 몇호실 방을 배정받을 지 모릅니다. 이때 여러분의 숙소 변수(room) 값은 널입니다. 체크인 하고 나면 변수 room은 구체적 값(= 345호)을 갖게 됩니다. 아직 객실이 확정되지 않았는데, 아무 호실이나 들어가면 안되잖아요(이런 상황을 NullPointerException이라고 하죠). 코틀린에서는 같은 타입이라도 널을 허용하는 타입과 널을 허용하지 않는 타입 2가지가 있습니다.
■ 널 타입 변수 선언 및 안전 호출(safe call)
위 문제를 해결하는 방법이 바로 안전 호출입니다. 먼저 isSmallCase()의 형식인자 s가 널 타입 변수를 허용하도록 s: String? 으로 수정합니다. s.isNullOrEmpty()에서 s 뒤에 물음표(?)를 붙여 s?.isNullOrEmpty()로 만들어 안전 호출로 수정합니다. 함수 isNullOrEmpty()는 수신자 객체가 널 또는 빈 문자열(empty string)일 때 true를 반환합니다.
fun isSmallCase(s: String?): Boolean {
if (s?.isNullOrEmpty() == true) return false
for (ch in s) {
if (!ch.isLowerCase( )) return false
}
return true
}
fun main() {
val foo: String? = null
println(isSmallCase(foo))
}
■ 스마트 타입 변환(smart cast)
재미있는 사실은 if 문에 이어서 for 문을 실행하면 for 문부터 s는 널이 아닌 객체입니다. 따라서 for 문부터는 안전 호출(?)을 사용하지 않아도 됩니다. IDEA에서 for 문의 s는 녹색으로 표시됩니다. 이 표시는 객체 s에 대해 스마트 타입 변환이 이루어졌음을 나타냅니다. 여기부터 문자열 객체 s가 널이 아닌 타입으로 바뀌었습니다.
아래처럼 코드를 추가해 보면 쉽게 알 수 있습니다. 변수 tmp의 타입을 널이 아닌 String 타입으로 선언했음에도 타입 불일치 에러가 발생하지 않습니다.
if (s?.isNullOrEmpty() == true) return false
val tmp: String = s // 타입 불일치 에러가 나타나지 않습니다.
for (ch in s) {
if (!ch.isLowerCase( )) return false
}
■ 실전 코드: 효과적인 널 타입 변수 처리 방법
간단히 널 타입 변수를 처리하는 실전 코드로 수정하겠습니다. 형식인자가 널 값을 갖는지 가장 먼저 조사합니다. 널이 아니라면 그 다음 문장부터는 스마트 타입 변환을 거친 형식인자를 사용할 수 있습니다. 안전 호출(?)은 사용할 필요가 없습니다. 안전 호출이 조금은 귀찮거든요...
fun isSmallCase(s: String?): Boolean {
if (s == null) return false // 가장 먼저 널인지 조사합니다.
if (s.isEmpty()) return false
for (ch in s) {
if (!ch.isLowerCase( )) return false
}
return true
}
'코틀린' 카테고리의 다른 글
1장 - 테스트에 도전해 보세요(정답 해설) (0) | 2024.09.14 |
---|---|
코틀린: 1장 테스트에 도전해 보세요 (0) | 2024.09.08 |
코틀린: String과 CharArray (0) | 2024.09.03 |
코틀린: String의 원소 참조 - 인덱싱 (0) | 2024.09.03 |
코틀린: package와 import(updated) (0) | 2024.09.03 |