정수(Integer) 타입
코틀린에서 모든 것은 객체로서 변수에 대한 멤버 함수나 프로퍼티를 호출할 수 있다. 어떤 타입은 특별한 내부 표현을 갖지만 사용자에게는 일반 클래스처럼 보인다.
숫자(Number)
코틀린은 자바와 유사한 방법으로 숫자를 다루는데 완전히 같지는 않다. 숫자에 대해 넓은 타입으로의 자동 변환이 없고, 어떤 경우에 리터럴도 약간 다르다.
코틀린이 제공하는 숫자 내장 타입은 다음과 같다.
타입 비트 크기 바이트 크기
Long | 64 | 8 |
Int | 32 | 4 |
Short | 16 | 2 |
Byte | 8 | 1 |
자바와 달리 코틀린에서 문자는 숫자가 아니다. 자바에선 다음과 같이 사용 가능하다. ex) char a = 'a'; System.out.printf("% d", a); -> 97이 출력된다.
명시적인 타입 지정 없이 변수를 초기화하면 컴파일러는 값을 나타내기에 충분한 최소 타입을 자동으로 유추한다. 만일 명시적으로 타입을 지정하고 싶다면 접미사를 쓰거나 변수에 타입을 지정해 주면 된다.
val one = 1 // Int
val threeBillion = 300000000 //Long
val oneLong = 1L
val oneByte: Byte = 1
부동 소수점 유형
실수의 경우 코틀린은 IEEE754 표준을 준수하는 부동 소수점 유형을 제공한다.
유형 비트 크기 바이트 크기
Double | 64 | 8 |
Float | 32 | 4 |
소수 부분이 있는 숫자로 변수를 Double 로 초기화할 수 있다.
val pi = 3.14 // Double
//val one: Double = 1 // Error: type mismatch
val oneDouble = 1.0 // Double
만약 Float형을 쓰고싶다면 접미사 f를 추가해 주면 된다. 이럴 경유 6-7자리 이상의 십진수가 포함된 경우 반올림된다.
val e = 2.7182818284 // Double
val eFloat = 2.7182818284f // Float, actual value is 2.7182817
가장 중요한 부분인데 코틀린은 일부 다른 언어(Java)와 달리 숫자에 대한 암묵적 변환이 불가능하다. 예를 들어 Double 변수에 Float, Int는 할당할 수 없다.
fun main() {
var i: Int = 1
val b: Byte = 1
val d = 1.0
val f = 1.0f
i = b //Error: Type mismatch
val a: Double = 1 //Error: Type mismatch
printDouble(i) //Error: Type mismatch
printDouble(d)
printDouble(f) //Error: Type mismatch
}
fun printDouble(d: Double) = print(d)
다른 언어에서 많이 봤던 암묵적 형 변환은 안된다.
형 변환은 명시적 변환을 사용하면 된다.
리터럴 상수
- 십진수 : 123
- Long은 대문자 L로 표시: 123L
- 16진수 : 0x0 F
- 2진수(바이너리) : 0b00001011
주의 : 8진수 리터럴은 지원하지 않는다.
부동 소수점을 위한 표기법을 지원한다.
- Double : 123.5, 1.235 e2
- Float : 123.5f
밑줄을 사용하여 상수를 읽기 쉽게 만들 수 있다.
val oneMillion = 1_000_000
val creditCardNumber = 1234_5678_8484_2222
표현
자바 플랫폼에서는 JVM기본 타입을 숫자로 저장한다. 만약 null 가능 숫자 레퍼런스(Int?)를 사용하거나 제네릭이 관여하면 박싱(boxing) 타입으로 저장한다. 숫자를 박싱 하면 값의 동등함은 유지되지만 동일성(identity, 주소까지 같은지)은 유지되지 않는다.
val a : Int = 100
val boxedA: Int? = a
val anotherBoxedA: Int? = a
val b : Int = 10000
val boxedB: Int? = b
val anotherBoxedB: Int? = b
println(boxedA == anotherBoxedA)//true출력
println(boxedB == anotherBoxedB)//true출력
println(boxedA === anotherBoxedA)//true출력
println(boxedB === anotherBoxedB)//false출력
‘==’는 값을 비교하고 ‘===’는 주소를 비교하기 때문에 결과에 차이가 있을 수 있다. 동일성이 유지되지 않기 때문이다.
하지만 ‘boxedA === anotherBoxedA’가 true로 출력되는 이유는 뭘까?
공식문서에선 다음과 같이 설명하고 있다.
All nullable references to a are actually the same object because of the memory optimization that JVM applies to Integers between -128 and 127. It doesn't apply to the b references, so they are different objects.
a를 참조한 모든 nullable 변수는 실질적으로 같은 객체이다. JVM이 -128 ~ 127 사이의 정수에 적용하는 메모리 최적화 때문이다. 그와 반대로 b는 초과하기 때문에 적용되지 않는다.
주소와 별개로 값은 여전히 같다.
val b: Int = 10000
println(b == b) // Prints 'true'
val boxedB: Int? = b
val anotherBoxedB: Int? = b
println(boxedB == anotherBoxedB) // Prints 'true'
명시적 변환
자바와 다르게 코틀린에선 타입 표현이 다르므로 작은 타입이 큰 타입의 하위 타입은 아니다. 하위 타입이 된다면 다음과 같은 문제가 발생한다.
자바처럼 byte(1) < short(2) < int(4) < long(8) < float(4) < double(8) 이 성립하지 않는 것을 의미한다.
// 실제로는 컴파일 되지않음
val a: Int? = 1//박싱된 Int
val b: Long? = a//자동 변환은 박싱된 Long생성
print(a == b)//놀랍게도 false를 리턴, Long의 equals()는 비교 대상의 타입도 검사하기 때문이다.
동일성뿐만 아니라 동등함 조차 모든 곳에서 나도 모르게 사라지게 된다. 이런 이유로 작은 타입을 큰 타입으로 자동 변환하지 않는다. 즉, 명시적 변환 없이 Byte타입 값을 Int 변수에 할당할 수 없음을 뜻한다.
val b: Byte = 1
val i: Int = b
val i: Int = b.toInt()
모든 숫자 타입은 다음 변환을 지원한다.
- toByte(): Byte
- toShort(): Short
- toInt(): Int
- toLong(): Long
- toFloat(): Float
- toDouble(): Double
- toChar(): Char
평소에 자동 변환의 부재를 거의 느낄 수 없는데 이는 타입을 컨텍스트에서 유추하고 적절한 변환을 위해 산술 연산이 오버로드되기 때문에 명시적 변환이 필요하지 않다. 예를 들면 다음과 같다.
val l = 1L + 3// Long + Int => Long
연산
비트 연산
이런 종류의 비트 연산이 있다는 정도로 알면 된다.
shl(bits) - 부호있는 왼쪽 시프트(자바의 <<)
shr(bits) - 부호있는 오른쪽 시프트(자바의 >>)
ushr(bits) - 부호없는 오른쪽시프트(자바의 >>>)
and(bits) - 비트 AND
or(bits) - 비트 OR
xor(bits) - 비트 XOR
inv() - 비트 역
비교 연산
동등비교 ==, ===
비교 | >, >=, <, <= |
범위 비교 | a..b, x in a..b, x !in a..b |
문자
Char타입으로 문자를 표현한다. 이 타입은 바로 숫자로 다룰 수 없다.
자바와 차이 Char a = 'a'는 자바에서 문자열 a 또는 ASCII인 97로 사용 가능하다. 코틀린에서 문자 a를 ASCII로 바꾸려면 a.code를 쓰면 된다.
특수문자는 이스케이프 백슬래시에서 시작한다.
- \t - 탭
- \b - 백스페이스
- \n - 새 줄(개 행)
- \r - 캐리지 리턴
- \’ - 작은따옴표
- \” - 큰 따옴표
- \\ - 백슬래시
- \$ - 달러 표시
숫자와 마찬가지로, 문자도 nullable 레퍼런스가 필요하면 박싱 하면 된다. 박싱 연산을 하면 주소가 달라지기 때문에 동일성은 유지되지 않는다.
만 문자 변숫값이 숫자인 경우 함수를 사용하여 명시적으로 숫자로 변환할 수 있는 digitToInt()가 있다.
Boolean
지연 논리 합 | || |
지연 논리 곱 | && |
역 | ! |
배열
Kotlin의 배열은 Array클래스로 표시된다. 이 클래스는 get, set, size 프로퍼티와 그 외 유용한 멤버 함수를 제공한다.(get, set은 연산자 오버로딩 관례에 따라 []를 사용할 수 있다.)
class Array<T> private constructor() {
val size: Int
operator fun get(index: Int): T
operator fun set(index: Int, value: T): Unit
operator fun iterator(): Iterator<T>
// ...
}
라이브러리 함수 arrayOf()를 사용해서 배열을 생성할 수 있다. 만약 null로 채운 배열이 필요하다면 arrayofNulls()를 사용하면 된다. 배열을 생성하는 또 다른 함수는 팩토리 함수(ex. Array(n){})를 사용하는 것이다. 팩토리 함수는 배열의 크기와 해당 인덱스에 위치한 요소의 초기값을 리턴하는 함수를 인자로 받는다.
//일반적인 배열 생성 방법
val ary = arrayOf(1, 2, 4)
//크기가 n이며 null값이 들어갈 수 있는 배열 생성
val ary1 = arrayOfNulls<Int>(5)
//크기가 5이며 원소는 0인 배열 생성. [0, 0, 0, 0, 0]
val ary1_1 = IntArray(5)
//크기가 5이며 원소는 2인 배열 생성. [2, 2, 2, 2, 2]
val ary1_2 = IntArray(5){2}
//크기가 5이며 중괄호 안에 들어간 람다의 계산 결과를 원소로 가지는 배열 생성. [0, 1, 4, 9, 16]
//여기서 it은 배열의 index를 참조한다.
val ary1_3 = Array(5){ (it*it).toString() }
//빈 정수배열 생성
val ary3 = emptyArray<Int>()
위처럼 별도의 Boxing overhead(ex. <Integer>, <String>, …) 없이 기본(primitive) 타입의 배열을 선언할 수 있다. 이들은 Array 클래스와 상속 관계에 있지 않지만 같은 메서드와 프로퍼티를 제공한다.
주의: 자바와 달리 코틀린의 배열은 무공변(invariant)이다. 이는 Array <String>를 Array <Any>에 할당할 수 없음을 의미하며, 런타임 실패를 방지한다. 하지만 리스코프 치환 법칙을 이용해서 Array <out String>을 사용할 수 있다.
List<Dog> dog = new ArrayList<Dog>(); List<Animal> animals = dogs; animals.add(new Cat()); Dog dog = dogs.get(0);
자바는 위와 같이 공변성을 가지므로 Dog객체 리스트에 같은 부모를 가지고 있는 Cat객체를 삽입할 수 있다.
무공변성(invariant) : 상속관계에 상관없이, 오직 자기 타입만 허용하는 것
공변성(covariant) : 타입 생성자에게 리스 코프 치환 법칙을 허용한다는 의미, 상속관계에 있는 객체도 허용, 즉 상위 타입에서 하위 타입으로 속성 변경없이 치환할 수 있다는 의미이다.
반공변성(contravariant) : 공변성의 반대, 자기 자신과 부모 객체만 허용하는 것
리스코프 치환 법칙(Liskov Substitution Principle) : 컴퓨터 프로그램에서 자료형 S가 자료형 T의 하위형이라면 필요한 프로그램의 속성의 변경 없이 자료형 T의 객체를 자료형 S의 객체로 교체(치환)할 수 있어야 한다는 원칙이다.
문자열 리터럴
표현방식으로 2가지가 있다.
- escaped 방식
val s = "hello \\n"
- row 방식
val s = """
for(c in foo){
${print(c)} //템플릿도 사용 가능
}
""".trimMargin()//trimMargin함수로 앞쪽 공백을 제거할 수 있다.
'코틀린' 카테고리의 다른 글
흐름제어 (0) | 2023.01.30 |
---|---|
타입 체크와 캐스트(is, as) (1) | 2023.01.30 |
기초구문, 이디엄(관용어) (0) | 2023.01.30 |
스코프 함수 (0) | 2022.07.24 |
16. 코루틴 기본 (0) | 2022.06.13 |