Kotlin은 모든것이 객체입니다. 그러므로 모든 변수(variable)에 대해 멤버 함수와 속성을 호출할 수 있습니다. 몇몇 타입은 특별한 내부 표현을 가집니다. 예를 들어 숫자(numbers), 문자(characters), 불리언(booleans) 타입은 실행시간에는 원시값 처럼 표현되어집니다. 그러나 사용자에게는 보통의 클래스로 보입니다. 이 글에서는 Kotlin에서 사용되는 기본 타입인 숫자, 문자, 불리언, 배열, 문자열에 대해서 알아봅니다.
1. 숫자(Numbers)
코틀린은 숫자를 자바와 유사하게 다룹니다. 하지만 똑같지는 않습니다. 예를 들어 코틀린의 숫자 타입은 암묵적인 넓은 범위로의 변환이 없습니다. 그리고 어떤 경우에는 리터럴이 약간 다릅니다.
※ 참고 : 자바에서 서로 다른 타입의 숫자를 연산하면 그 결과는 범위가 넓은쪽 타입으로 암묵적으로 변환되어 집니다.
코틀린은 다음과 같은 숫자를 표현하는 내장 타입을 제공합니다.
타입 | 비트 크기 |
---|---|
Double | 64 |
Float | 32 |
Long | 64 |
Int | 32 |
Short | 16 |
Byte | 8 |
주의할 점은 코틀린에서 문자(character) 타입은 숫자가 아닙니다.
1.1 리터럴 상수(Literal Constants)
정수에 대해서 다음과 같은 종류의 리터럴 상수가 있습니다.
- 십진수: 123
- Long 값은 대문자 L 을 붙임: 123L
- 16진수: 0x0F
- 이진수: 0b00001011
8진수 리터럴은 지원되지 않습니다.
또한 코틀린은 소수점을 가진 숫자에 대해 관습적인 표기법을 지원합니다.
Double(기본 타입): 123.5, 123.5e10
Float는 f 또는 F 를 붙임: 123.5f
1.2 숫자 리터럴에 밑줄(_) 사용(1.1 이후로 사용)
숫자 상수를 읽기 쉽도록 하기 위해서 밑줄을 사용할 수 있습니다.
val oneMillion = 1_000_000
val creditCardNumber = 1234_5678_9012_3456L
val socialSecurityNumber = 999_99_9999L
val hexBytes = 0xFF_EC_DE_5E
val bytes = 0b11010010_01101001_10010100_10010010
1.3 표현(Representation)
자바 플랫폼에서 숫자는 null가능한 숫자 참조(예를 들면 Int?) 또는 제네릭 관련이 아닌한 물리적으로 JVM 원시 타입으로 저장됩니다. 후자의 경우 숫자가 박싱됩니다.
※ 참고 : 코틀린 언어는 컴파일시 자바 가상머신(JVM)상에서 수행되는 코드로 만들어지고, JVM 상에서 수행됩니다. 자바 1.5 부터 박싱과 언박싱이 지원됩니다. 박싱은 원시 타입의 데이터를 연산에 사용하면 자동으로 객체로 변환 되는 것을 말하고, 언박싱은 숫자 래퍼 클래스가 자동으로 원시 타입으로 변환되는 것을 말합니다.
숫자를 박싱한 것이 반드시 같은 객체는 아닙니다.
val a: Int = 10000
println(a === a) // Prints 'true'
val boxedA: Int? = a
val anotherBoxedA: Int? = a
println(boxedA === anotherBoxedA) // !!!Prints 'false'!!!
반면에 값은 동일합니다.
val a: Int = 10000
println(a == a) // Prints 'true'
val boxedA: Int? = a
val anotherBoxedA: Int? = a
println(boxedA == anotherBoxedA) // Prints 'true'
※ 참고 : 위 내용은 객체의 비교에 대해서 예기하고 있습니다. 이퀄을 세개 사용하는(===) 연산자는 동일한 객체인지 확인합니다. 객체의 값이 같더라도 객체 자체가 다르면 false가 됩니다. 이퀄을 두개 사용하는(==) 연산자는 객체의 값을 비교합니다. 서로 다른 두개의 객체이더라도 가지고 있는 값이 같으면 true 를 반환합니다.
1.4 명시적인 변환(Explicit Conversions)
표현(representation)이 다름으로 인해 작은 범위의 타입은 그것보다 큰 타입의 서브 타입이 아닙니다. 만약 서브타입이 된다면 다음과 같은 문제를 겪게 될 것입니다.
// 가상 코드가 실제로 컴파일 되지 않습니다:
val a: Int? = 1 // Int로 박싱 됩니다(java.lang.Integer).
val b: Long? = a // 암묵적인 형변환이 Long으로 박싱된 결과를 냅니다(java.lang.Long).
print(b == a) // Long의 equals() 메소드가 다른 것도 Long인지 체크하면서 false를 출력합니다.
그래서 객체의 동일성(identity)은 말할것도 없고, 값의 동일성(equality)은 모든곳에서 조용히 잃어버렸을 것입니다.
결과적으로, 작은 범위의 타입은 큰 범위의 타입으로 암묵적 변환이 일어나지 않습니다. 즉, 명시적인 형변환 없이 Byte 타입의 값을 Int 타입의 변수에 할당할 수 없습니다.
val b: Byte = 1 // OK, 리터럴은 정적으로 체크됩니다.
val i: Int = b // 에러
우리는 숫자의 범위를 넓히기 위해 명시적인 형변환을 사용할 수 있습니다.
val i: Int = b.toInt() // OK: 명시적인 형변환
print(i)
모든 숫자 타입은 다음의 형변환을 지원합니다.
- toByte(): Byte
- toShort(): Short
- toInt(): Int
- toLong(): Long
- toFloat(): Float
- toDouble(): Double
- toChar(): Char
코틀린에서 암묵적 형변환이 없다는 것이 크게 불편하게 느껴지지 않습니다. 이유는 타입이 문맥에서 추론되어지고, 산술연산에서는 적절히 오버로드 되어 있기 때문입니다.
val l = 1L + 3 // Long + Int => Long
1.5 연산(Operations)
코틀린은 숫자에 대한 표준 산술연산 세트를 지원합니다. 이 연산 기능은 적절한 클래스들의 멤버로 선언되어 있습니다.
비트 연산을 나타내는 특수 문자는 없습니다. 하지만 비트 연산을 수행하는 함수가 infix 형식으로 호출되어질 수 있습니다.
val x = (1 shl 2) and 0x000FF000
※ 참고 : infix 형식이란 코틀린에서 함수 호출시 .이나 () 없이 호출할 수 있는 것을 말합니다. 위의 (1 shl 2)는 1.shl(2)를 infix 형식으로 표기한 것입니다.
비트 연산은 Int 와 Long에서만 사용됩니다. 아래는 비트 연산자의 전체 목록입니다.
- shl(bits) : signed shift left (Java의 <<)
- shr(bits) : signed shift right (Java의 >>)
- ushr(bits) : unsigned shift right (Java의 >>>)
- and(bits) : 비트e and
- or(bits) : 비트 or
- xor(bits) : 비트 xor
- inv() : 비트 반전
1.6 부동 소수점 숫자 비교(Floating Point Numbers Comparison)
이 절에서 설명하는 부동 소수점 숫자에 대한 연산은 다음과 같습니다.
값이 동일한지 검사: a == b and a != b
비교 연산자: a < b, a > b, a <= b, a >= b
범위 인스턴스화 및 범위 검사: a..b, x in a..b, x !in a..b
※ 참고 : 위에서 범위 인스턴스화라는 것은 범위내에서 값을 하나씩 꺼내서 사용할 수 있도록 하는 것을 말합니다.
피연산자 a와 b가 Float, Double 또는 이들의 null이 가능한 대응되는 객체(정의되었거나 추론되었거나 스마트 캐스트의 결과로 나온)로 정적으로 알려진 경우, 숫자 및 이들이 형성하는 범위에 대한 연산은 부동소수점 연산을 위한 IEEE 754 표준을 따릅니다.
※ 참고 : IEEE 754 표준은 부동 소수점 숫자를 표현하는데 가장 널리 사용되는 표준입니다. 자세한 사항은 위키의 IEEE 754 페이지를 참조하세요.
그러나, 피연산자가 부동 소수점 숫자로 정적으로 유형화 되지 않은 경우(예를 들어 Any, Comparable<...>, 형식 매개변수(type parameter)), 제네릭 사용을 지원하고 전순서(total ordering)를 제공하기 위해, 연산에서는 Float 및 Double에 대해 equals와 compareTo 구현을 사용하는데, 이것은 다음에서 나오는 것과 같이 표준과 일치하지 않습니다.
+ NaN 은 자신과 동등합니다.
+ NaN 은 POSITIVE_INFINITY를 포함한 다른 요소보다 더 큽니다.
+ -0.0 은 0.0 보다 작습니다.
※ 참고 : 전순서(Total Order)는 집합내의 모든 요소들이 비교 가능한 것을 말합니다. Flot와 Double에서 지원하는 <= 와 같은 것은 모든 Float, Double 타입 값에 대해 동작해야 하므로 전순서가 되어야 합니다. 위의 설명은 이러한 기능을 정적으로 유형화 되지 않을 경우에도 지원하기 위해서 표준을 따르지 않는 부분도 있다는 뜻입니다.
※ 참고 : 형식 매개변수(Type parameter)는 제네릭을 정의할 때 사용하는 변수 T를 뜻합니다. 실제 사용시 구체적인 타입으로 대체 됩니다.
2. 문자(Characters)
문자는 Char 타입으로 표현됩니다. 문자 타입은 숫자로 직접 취급될 수 없습니다.
fun check(c: Char) {
if (c == 1) { // ERROR: incompatible types
// ...
}
}
문자 리터럴은 작은 따옴표를 사용하여 표현합니다(예: '1'). 특수문자는 백슬래시를 사용해서 이스케이프 됩니다. 다음 이스케이크 시퀀스가 지원됩니다(\t, \b, \n, \r, \', \", \\, \$). 다른 문자를 인코딩하려면 유니코드 이스케이스 시퀀스를 사용합니다(예: '\uFF00').
문자는 숫자로 명시적으로 변환할 수 있습니다.
fun decimalDigitValue(c: Char): Int {
if (c !in '0'..'9')
throw IllegalArgumentException("Out of range")
return c.toInt() - '0'.toInt() // 숫자로 명시적으로 변환합니다.
}
숫자처럼 문자는 null 가능한 참조가 필요할때 박싱됩니다. 박싱될때 동일한 객체로 만들지지 않습니다(다른 객체로 만들어 집니다).
3. 불리언(Booleans)
Boolean 타입은 참과 거짓 두 가지 값만을 가지는 타입입니다(true와 false).
Boolean은 null 가능한 참조가 필요할때 박싱 됩니다.
Boolean인 가지고 있는 내장 연산자는 다음과 같습니다.
+ || - lazy disjunction
+ && - lazy conjunction
+ ! - negation
4. 배열(Arrays)
코틀린에서 배열은 Array 클래스로 나타내 집니다. 이 클래스는 get, set 함수(이 함수는 오버로딩 관습에 따라 [] 연산자로 전환됩니다)와 size 속성을 가집니다. 덧붙여서 몇가지 유용한 멤버 함수들이 제공됩니다.
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()에 아이템 값들을 파라미터로 넣습니다. 그래서 arrayOf(1, 2, 3)으로 배열 [1, 2, 3]을 만듭니다. 그렇지 않으면 arrayOfNulls() 라이브러리 함수를 사용해서 지정된 크기의 null로 채워진 배열을 만들 수 있습니다.
또 다른 옵션은 배열 크기를 파라미터로 가지는 생성자와 지정된 배열 인덱스에 해당하는 초기 배열 요소값을 반환할 수 있는 함수를 사용하는 것입니다.
// Creates an Array<String> with values ["0", "1", "4", "9", "16"]
val asc = Array(5) { i -> (i * i).toString() }
asc.forEach { println(it) }
앞에서 말한 것처럼 [] 연산자는 get(), set() 멤버 함수를 호출합니다.
참고: 자바와 달리 코틀린의 배열은 불변입니다. 이것의 의미는 코틀린은 Array<String>을 Array<Any>에 할당하지 못한다는 것입니다. 이것은 런타임에 발생할 오류를 방지합니다(그러나 Array<out Any>는 사용할수 있습니다).
코틀린은 또한 박싱 오버헤드 없이 원시 타입의 배열을 나타내는 특수한 클래스를 가지고 있습니다(ByteArray, ShortArray, IntArray 등). 이러한 클래스들은 Array클래스와 상속 관계는 없지만 같은 메소드와 프로퍼티들은 가지고 있습니다. 각각은 또한 대응되는 팩토리 함수를 가지고 있습니다.
val x: IntArray = intArrayOf(1, 2, 3)
x[0] = x[1] + x[2]
5. 부호없는 정수(Unsigned integers)
부호없는 정수는 코틀린 1.3이후에서만 사용 가능하고 현재는 실험중입니다.
코틀린은 부호없는 정수에 대해 다음 타입을 소개합니다.
kotlin.UByte: an unsigned 8-bit integer, ranges from 0 to 255
kotlin.UShort: an unsigned 16-bit integer, ranges from 0 to 65535
kotlin.UInt: an unsigned 32-bit integer, ranges from 0 to 2^32 - 1
kotlin.ULong: an unsigned 64-bit integer, ranges from 0 to 2^64 - 1
부호없는 타입은 그것의 부호있는 타입의 대부분의 연산을 지원합니다.
부호없는 정수에서 부호있는 정수로의 형변환(그리고 그 반대의 경우)는 호환성이 없는 이진 변경이라는 것에 주의해야 합니다.
부호없는 정수는 도른 실험적인 기능이 인라인 클래스를 사용해서 구현됩니다.
5.1 특수 클래스(Specialized classes)
원시 타입과 마찬가지로 각 부호없는 타입에는 해당 타입에 특화된 배열이 있습니다.
kotlin.UByteArray: an array of unsigned bytes
kotlin.UShortArray: an array of unsigned shorts
kotlin.UIntArray: an array of unsigned ints
kotlin.ULongArray: an array of unsigned longs
부호 있는 정수 배열과 마찬가지로, 박싱 오버헤더가 없는 Array 클래스와 유사한 API를 제공합니다.
또한 UInt와 ULong에 대해 kotlin.ranges.UIntRange, kotlin.ranges.UIntProgression, kotlin.ranges.ULongRange, kotlin.ranges.ULongProgression 클래스로 범위와 형변환(progressions)을 지원합니다.
5.2 리터럴(Literals)
부호없는 정수를 쉽게 만들기 위해서 코틀린은 Float/Long처럼 부호없는 정수를 나타내는 접미사를 제공합니다.
접미사 u 와 U를 붙이면 부호없는 정수입니다. 정확한 타입은 기대되는 타입을 기반으로 결정되어 집니다. 기대하는 타입이 제공되지 않는다면 UInt와 ULong은 리터럴의 크기를 기반으로 선택되어집니다.
val b: UByte = 1u // UByte, expected type provided
val s: UShort = 1u // UShort, expected type provided
val l: ULong = 1u // ULong, expected type provided
val a1 = 42u // UInt: no expected type provided, constant fits in UInt
val a2 = 0xFFFF_FFFF_FFFFu // ULong: no expected type provided, constant doesn't fit in UInt
접미사 uL과 UL은 명시적으로 부호없는 Long타입을 지정합니다.
val a = 1UL // ULong, even though no expected type provided and constant fits into UInt
5.3 unsigned 정수의 실험적인 상태
unsigned 타입의 디자인은 실험적입니다. 이말은 이 기능이 빠르게 움직고 있고, 호환성이 보증되지 않는다는 뜻입니다. Kotlin 1.3+에서 unsigned 산술연산을 사용할 때 이 기능은 실험적인 기능입니다 라는 경고가 보여집니다. 이 경고를 제거하려면 unsigned 타입의 시험사용을 opt-in하여야 합니다.
unsigned 타입을 opt-in 하는 두 가지 방법이 있습니다. API를 experimental로 표시하거나 표시하지 않는 경우입니다.
- 실험성을 전파하려면 unsigned 정수를 선언하는 곳에 @ExperimentalUnsignedTypes 아노테이션을 붙이거나, 컴파일러에 -Xexperimental=kotlin.ExperimentalUnsignedTypes 옵션을 사용합니다.(후자는 컴파일된 모듈의 모든 선언을 실험으로 만들것 입니다.)
- 실험성 전파없이 opt-in 하기 위해서는 선언에 @UseExperimental(ExperimentalUnsignedTypes::class) 아노테이션을 붙이거나 컴파일러에 -Xuse-experimental=kotlin.ExperimentalUnsignedTypes 옵션을 사용합니다.
당신의 고객들이 당신의 API 사용을 명시적으로 동의해야 하는지는 당신의 결정에 달려있습니다. 그러나 부호없는 타입은 실험적인 기능임을 명심하세요. 그러므로 이러한 API는 언어의 변화로 갑자기 동작하지 않을 수 있습니다.
자세한 내용은 실험 API KEEP를 참조하십시오.
5.4 추가 논의
기술적 상세정보와 추가 논의는 unsigned 타입의 언어 제안을 참조하십시오.
6. 문자열(Strings)
문자열은 String 타입으로 선언합니다. 문자열을 불변(immutable)입니다. 문자열의 요소는 문자로 인덱싱 연산자로 액세스할 수 있습니다: s[i]. 문자열은 for 루프로 반복할 수 있습니다.
for (c in str) {
println(c)
}
문자열은 + 연산자를 사용해서 연결할 수 있습니다. 연산의 첫번째 요소가 문자열이면 다른 타입의 값도 연결할 수 있습니다.
val s = "abc" + 1
println(s + "def")
대부분분의 경우 문자열 템플릿이나 raw 문자열을 사용하는것이 문자열 연결보다 더 좋습니다.
6.1 문자열 리터럴(String Literals)
코틀린에는 두가지 타입의 문자열 리터럴이 있습니다.
- 이스케이프된 문자열 : 이스케이프된 문자를 가지고 있는 문자열 입니다.
- raw 문자열 : 개행문자나 임의의 글을 가진 문자열 입니다.
이스케이프된 문자열은 자바 문자열과 매우 유사합니다.
val s = "Hello, world!\n"
이스케이핑은 백슬리쉬를 사용한 관습적인 방법으로 수행됩니다. 지원되는 이스케이프 시퀀스는 위의 문자 부분을 보세요.
raw 문자열은 세개의 큰따옴표로 구분되며 이스케이핑을 포함하지 않고, 개행문자와 다른 문자를 포함할 수 있습니다.
val text = """
for (c in "foo")
print(c)
"""
문자열 앞의 공백을 제거하려면 trimMargin() 함수를 사용할 수 있습니다.
val text = """
|Tell me and I forget.
|Teach me and I remember.
|Involve me and I learn.
|(Benjamin Franklin)
""".trimMargin()
기본값으로 | 가 마진 접두사로 사용되지만, 함수의 파라미터로 다른 문자를 넣어서 바꿀수 있습니다.(예: trimMargin('>'))
6.2 문자열 템플릿(String Templates)
문자열 리터럴은 템플릿 표현을 포함할 수 있습니다. 즉, 문자열 템플릿은 평가되어져 문자열로 연결되어 집니다. 템플릿 표현은 $기호로 시작하고 간단한 이름이 붙는 구조 입니다.
val i = 10
println("i = $i") // prints "i = 10"
또는 중괄호 안에 들어가는 임의의 표현식입니다.
val s = "abc"
println("$s.length is ${s.length}") // prints "abc.length is 3"
템플릿은 raw 문자열 내와 이스케이프된 문자열 내에서도 지원됩니다. 만약 raw 문자열(백슬래쉬 이스케이핑을 지원하지 않음) 내에서 $ 문자를 표현해야 할때는 다음 구문을 사용할수 있습니다.
val price = """
${'$'}9.99
"""
'프로그래밍 > Kotlin' 카테고리의 다른 글
Kotlin - 흐름제어(Control Flow): if, when, for, while (1) | 2019.07.13 |
---|---|
Kotlin - 패키지와 임포트(packages and imports) (0) | 2019.07.05 |
Kotlin - 코딩 관습(Coding Conventions) (0) | 2019.06.09 |
Kotlin - 관용구(Idioms) (0) | 2019.05.06 |
Kotlin - Basic Syntax(기본 구문) (2/2) (0) | 2019.04.30 |