Kotlin(4) - 코틀린 자료형 검사 & 변환
코틀린은 변수를 사용할 때 반드시 값이 할당되어 있어야 한다는 원칙이 있습니다.
만약, 값이 할당되지 않은 변수를 사용하면 오류가 발생하는데,
값이 없는 상태를 null이라고 부릅니다.
null 상태인 변수를 허용하려면 물음표(?) 기호를 사용해 선언해야 합니다.
1. null을 허용한 변수 검사하기
프로그램이 실행되는 도중에 값이 null인 변수에 접근하려 하면 NPE 예외 오류가 발생합니다.
그러나 코틀린은 변수에 아예 null을 허용하지 않습니다.
-
변수에 null 할당하기
var str1 : String = "Hello Kotlin" //str1 = null => 오류 null을 허용하지않음 println("str1: $str1")
위의 코드를 실행하면 null 값이 할당될 수 없다는 오류 메세지가 나타납니다.
변수에 null 할당을 허용하려면 자료형 뒤에 물음표(?) 기호를 명시해야 합니다.var str1 : String? = "Hello Kotlin" str1 = null println("str1: $str1")
str1 변수에 null을 할당할 수 있으며, 변수의 null 허용 여부에 따라
String과 String?은 서로 다른 자료형입니다. -
세이프 콜과 non-null 단정 기호
var str1: String? = "Hello Kotlin" str1 = null println("str1: $str1 length: ${str1.length}") //null을 허용하면 length가 실행될 수 없음
위 코드는 실행이 불가능하며, String?형에서는 세이프 콜(?.)이나 non-null 단정 기호(!!.)만 허용합니다.
-
세이프콜
null이 할당되어 있을 가능성이 있는 변수를 검사하여 안전하게 호출하도록 도와주는 기법
var str1: String? = "Hello Kotlin" str1 = null println("str1: $str1 length: ${str1?.length}") // str1을 세이프 콜로 안전하게 호출
str1을 검사한 다음 null이 아니면 str1의 멤버 변수인 length에 접근해 값을 읽습니다.
반면, str1에 아무런 값이 없을 경우 length에 접근하지 않고 그대로 null을 출력합니다. -
non-null 단정기호
non-null 단정 기호는 변수에 할당된 값이 null이 아님을 단정하는 기호입니다.
그러므로 컴파일러가 null 검사 없이 무시합니다.따라서 변수에 null이 할당되어 있어도 컴파일은 진행되나, 실행 중에 NPE가 발생합니다.
println("str1: $str1 length: ${str1!!.length}") //NPE 강제 발생
-
-
조건문을 활용한 null을 허용한 변수 검사
세이프 콜이나 단정 기호를 사용하는 방법 대신 조건문으로 null을 허용한 변수를 검사해도 됩니다.
즉, null을 허용한 변수의 null 상태 가능성을 검사하기만 하면 코틀린 컴파일러는 오류를 발생시키지 않습니다.fun main(){ var str1: String? = "Hello Kotlin" str1 = null var len = if(str1 != null) str1.length else -1 println("str1: $str1 length: ${len}") }
-
세이프 콜과 엘비스 연산자 활용
null을 허용한 변수를 조금 더 안전하게 사용하려면 세이프 콜(?.)과
엘비스(Elvis) 연산자 ?:를 함께 사용하면 됩니다.엘비스 연산자는 변수가 null인지 아닌지 검사하여 null이 아니라면,
그대로 왼쪽 식을 실행하고 null이면 오른쪽 식을 실행합니다.var str1: String? = "Hello Kotlin" str1 = null println("str1 = $str1 length: ${str1?.length ?: -1}") //세이프 콜과 엘비스 연산자 활용
위의 ${str1?.length ?: -1}은 if(str1!=null) str1.length else -1과 동일합니다.
2. 자료형 비교하고 검사하고 변환하기
코틀린의 자료형은 모두 참조형으로 선언한다고 배웠습니다.
하지만 컴파일될 때 Int, Long, Short와 같은 자료형은 기본형 자료형으로 변환됩니다.
각각 저장방식이 다르기 때문에 자료형을 비교하거나 검사할 때는 이런 특징을 잘 이해해야 합니다.
코틀린은 자료형이 서로 다른 변수를 비교하거나 연산할 수 없습니다.
-
자료형 변환
코틀린은 자바같은 언어처럼 자료형이 다른 변수에 재할당 하면 자동 형변환이 되지 않습니다.
(Type Mismatch 오류 발생)val a: Int = 1 //Int형 변수 a를 선언하고 1을 할당 val b: Double = a //자료형 불일치로 오류 발생 val c: Int = 1.1 //자료형 불일치 오류 발생
만약 자료형을 변환해 할당하고 싶다면 자료형 변환 메서드가 필요합니다.
val b: Doulbe = a.toDouble()
그러나 표현식에서 자료형이 서로 다른 값을 연산할 때는 자료형이 표현할 수 있는
범위가 큰 자료형으로 자동 형 변환하여 연산합니다.var result = 1L + 3 //Long + Int -> result는 Long
-
기본형과 참조형 자료형의 비교 원리
자료형을 비교할 때 단순히 값만 비교하는 방법과 참조 주소까지 비교하는 방법이 있습니다.
단순히 값만 비교할 경우 이중 등호 (==)를 사용하며
(주소는 상관 없음)
참조 주소를 비교하려면 삼중 등호(===)를 사용합니다.
(값은 상관 없음)val a: Int = 128 val b: Int = 128 println(a == b) println(a === b)
위의 코드의 경우는 Int형 같은 경우 컴파일러가 자동으로 int로 변환해주기 때문에,
삼중 등호가 비교하는 값도 저장된 값이 128입니다.참조 주소까지 달라지는 것은 null을 허용한 변수의 경우 같은 값을 저장해도
이중등호와 삼중등호를 사용한 결과가 다릅니다.val a: Int = 128 //스택 저장 val b: Int? = 128 // 참조형으로 저장 println(a == b) println(a === b)
Int형으로 선언된 a는 기본형으로 변환되어 스택에 128이라는 값 자체를 저장합니다.
Int?형으로 선언된 b는 참조형으로 저장되므로 b에는 128이 저장된 힙의 참조 주소가 저장되어 있습니다.즉, a와 b를 삼중 등호로 비교하면 false가 나옵니다.
fun main() { val a: Int = 128 val b = a println(a === b) val c: Int? = a val d: Int? = a val e: Int? = c println(c == d) println(c === d) println(c === e) }
위 코드에서 a와 b는 참조형인 Int로 선언되었지만
코틀린 컴파일러에 의해 기본형으로 변환되어 값 자체가 스택에 저장되어 있고
c, d, e는 참조형으로
d와 e는 힙 메모리에 있는 동일한 객체를 참조하고 c는 다른 객체를 참조합니다
따라서 c == d는 값을 비교함으로 같지만 c === d는 주소를 비교하므로 false를 출력합니다.
c와 e의 경우에는 e가 c의 참조주소를 저장하기 때문에 이중, 삼중등호로 비교한 값이 모두 동일합니다. -
스마트 캐스트 알아보기
만약 어떤 값이 정수일 수도 있고, 실수일 수도 있다면
그때마다 자료형을 변환해도 되지만 컴파일러가 자동으로 형 변환을 하는 스마트 캐스트를 사용하는 것이 더 편리합니다.대표적으로 스마트 캐스트가 적용되는 자료형은 Number형이 있습니다.
Number형을 사용하면 숫자를 저장하기 위한 특수한 자료형 객체를 만듭니다.
Number형으로 정의된 변수에는 저장되는 값에 따라 정수형이나 실수형 등으로 자료형이 변환됩니다.fun main(){ var test: Number = 12.2 // 12.2로 인해 Float형으로 스마트 캐스트 test = 12 // Int형으로 스마트 캐스트 test = 120L // Long형으로 스마트 캐스트 test += 12.0f // Float형으로 스마트 캐스트 }
위의 코드는 Number형과 스마트 캐스트를 사용한 예제입니다.
-
자료형 검사하기
변수의 자료형을 알아내는 방법은 is 키워드를 사용하는 것입니다.
is는 왼쪽 항의 변수가 오른쪽 항의 자료형과 같으면 true, 아니면 false를 반환합니다.fun main() { val num = 256 if(num is Int){ // num이 Int일 때 print(num) }else if(num !is Int){ // num이 Int형이 아닐 때, !(num is Int)와 동일하다 print("Not a Int") } }
is는 변수의 자료형을 검사한 다음 그 변수를 해당 자료형으로 변환하는 기능도 있습니다.
(이건 나중에 더 보겠습니다.)Any형을 사용하면 자료형을 결정하지 않은 채로 변수를 선언할 수 있습니다.
Any형은 코틀린의 최상위 기본 클래스로 어떠한 자료형으로도 변환될 수 있는 특수한 자료형입니다.
이때 is를 사용하여 자료형을 검사하면 검사한 자료형으로 스마트 캐스트 됩니다.val x: Any x = "Hello" if(x is String){ print(x.length) // x는 자동으로 String으로 스마트 캐스트 }
-
as에 의한 스마트 캐스트 as로 스마트 캐스트 할 수도 있는데, as는 형 변환이 가능하지 않으면 예외를 발생시킵니다.
var x: String = y as String
위의 코드에서 y가 null이 아니면 String으로 형 변환되어 x에 할당됩니다.
y가 null이면 형 변환을 할 수 없으므로 예외가 발생합니다.null 가능성을 고려하여 예외 발생을 피하려면 ? 기호를 사용해야합니다.
var x: String? = y as? String
-
묵시적 변환 Any형은 자료형이 특별히 정해지지 않은 경우에 사용합니다.
코틀린의 Any형은 모든 클래스의 뿌리이고, Int나 String, 사용자가 만든 클래스
모두 Any형의 자식 클래스입니다.
즉 코틀린의 모든 클래스는 Any 라는 슈퍼 클래스를 가집니다.fun main(){ checkArg("Hello") checkArg(5) } fun checkArg(x: Any){ if(x is String){ println("x is String: $x") } if(x is Int){ println("x is Int: $x") } }
위의 코드처럼 함수와 판단문을 사용해 언제든지 필요한 자료형에 따른 역할을 지정하여 처리할 수 있습니다.
3. 코틀린 연산자
-
기본 연산자
코틀린의 기본 연산자로는 산술, 대입, 증가, 감소, 비교, 논리연산자가 있습니다.
산술: +, -, _, /, %
대입: =, +=, -=, _=, /=, %= (가장 낮은 우선순위)
증감: ++, – (단항 연산자로 위치에 따라 연산이 달라짐)
비교: >, <, >=, <=, ==, !=, ===, !==
논리: &&, ||, !비트 연산자는 기계가 이해할 수 있는 값인 0과 1을 처리하는데 사용합니다.
보통 기기를 직접 제어할 때 사용합니다.비트 연산을 위한 비트 메서드로는
.shl(bits) : 비트를 bits만큼 왼쪽으로 이동(부호 있음)
.shr(bits) : 비트를 bits만큼 오른쪽으로 이동(부호 있음)
.ushr(bits) : 비트를 bits만큼 오른쪽으로 이동(부호 없음)
.and(bits) : 비트와 bits를 표현하는 비트로 논리곱 연산
.or(bits) : 비트와 bits를 표현하는 비트로 논리합 연산
.xor(bits) : 비트와 bits를 표현하는 비트의 배타적 연산 .inv() : 비트를 다 뒤집음 -
비트 이동 연산자 위에서는 메서드를 살펴봤고 이제 연산자를 보겠습니다.
shl, shr은 각각 비트를 왼쪽, 오른쪽으로 밀어낸 다음 사라진 비트의 값은 0으로 두고 부호 비트는 그대로 둡니다.
ushr은 제일 왼쪽 비트에 0을 밀어 넣으면서 오른쪽으로 비트가 이동합니다.
즉, 부호 비트까지 포함하여 비트를 밀어냅니다.
이러한 특징 때문에 음수인 경우 주의해서 사용해야 합니다.논리합 연산자 or은 두 수의 비트를 일대일 대응으로 비교하며, 하나라도 1이면 1을 반환합니다.
논리곱 연산자 and는 두 수의 비트를 일대일 대응으로 비교하며, 둘 다 1이면 1을 반환합니다.배타적합 연산자 xor은 두 비트 값을 비교하여 같으면 0을 다르면 1을 반환합니다.
반전 연산자 inv는 비트를 다 뒤집습니다.
댓글남기기