코틀린에서 모든 것은 우리가 어떤 변수에 대해 멤버 함수와 Properties를 호출 할 수 있다는 점에서 객체이다. 일부 유형은 특수한 내부 표현을 가질 수 있다. 예를 들어 숫자, 문자, Boolean은 런타임 시에 primitive value로 표현될 수 있지만, 사용자가 보기엔 ordinary class(일반 클래스)로 보인다.
Numbers
Integer types
코틀린에서는 숫자를 표현하기 위해 여러개의 내장 타입을 제공한다.
Type | Size(bits) | Min value | Max value |
Byte | 8 | -128 | 127 |
Short | 16 | -32768 | 32767 |
Int | 32 | \(-2^{31}\) | \(2^{31}-1\) |
Long | 64 | \(-2^{63}\) | \(2^{63}-1\) |
코틀린에서 정수 값의 기본 자료형은 Int형이다. Int형의 최대 범위를 넘지 않는 값으로 초기화된 모든 변수는 Int형으로 타입 추론한다.
만약, Int형의 최대 범위를 넘는 경우 Long형으로 추론한다. (명시적으로 Long형임을 지정하려면 값에 접미사 L을 사용한다.)
Floating-point types
Type | Size(bits) | Significant bits | Exponent bits | Decimal digits |
Float | 32 | 24 | 8 | 6-7 |
Double | 64 | 53 | 11 | 15-16 |
코틀린에서 소수 값의 기본 자료형은 Double형 이다. (명시적으로 Float형임을 지정하려면 값에 접미사 f 또는 F 사용
위 코드를 실행해보면 double형은 오차 없이 값이 저장되지만, Float형은 오차가 생김을 확인할 수 있다.
부동 소수점 형에서 오차는 2진수 반올림 규칙을 적용한다.
- 반올림할 위치 다음 비트가 0이면 버린다.
- 반올림할 위치 다음 비트가 1이고 뒤의 비트 중 하나라도 1이 있으면 올린다.
- 반올림할 위치 다음 비트가 1이고 뒤의 비트가 모두 0이면 짝수가 되도록 버리거나 올린다.
🐯 코틀린은 자바와 다르게 광역화 형변환(값의 최대 범위가 더 큰 쪽으로 암묵적 형변환)을 지원하지 않는다.
Literal constants
- 정수형 : 10진수(123), 16진수(0x0F), 2진수(0b00001011) 지원
- 실수형 : 부동소수점에 대한 기존 표현 지원
- Double : 123.5, 123.5e10
- Float : 123.5f, 123.5F
🐯 리터럴 상수를 읽기 쉽게 하기 위해 _ 를 사용할 수 있다
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
Numbers representation on the JVM
코틀린이 JVM 플랫폼위에서 실행될 때, 숫자는 상응하는 기본형으로 저장한다.
단, nullable 숫자 참조를 만드는 경우 (Int?) 또는 제네릭스를 사용하는 경우 Java 클래스(Integer, Double)로 박싱한다.
nullable 숫자 참조
nullable 숫자 참조의 경우 동일한 숫자여도 다른 객체일 수 있다.
JVM은 -128 .. 127 사이의 Integer 객체를 캐싱해놓고 필요할 때마다 참조해서 사용한다. → 해당 숫자 범위의 정수 값이 저장되어있는 경우 === 연산자로 비교하면 true를 반환, 해당 숫자 범위가 아닌 경우 false를 반환한다.
Explicit conversions
코틀린에서 작은 타입의 큰 타입으로의 암묵적 형변환(광역화 형변환)이 불가능하다. 내부적으로 다른 표현 때문인데, 작은 타입이 큰 타입이 서브타입이 아니기 때문이다.
number 타입 간에 작은 타입을 큰 타입으로 변환하기 위해서 변환 함수를 지원한다.
- toByte(): Byte
- toShort(): Short
- toInt(): Int
- toLong(): Long
- toFloat(): Float
- toDouble(): Double
- toChar(): Char
타입이 컨텍스트로부터 추론될 수 있는 경우와 산술연산의 경우 적절한 변환을 자동으로 수행하도록 오버로딩 되어있기 때문에 명시적 형변환이 필요하지 않다.
val result = 1L + 3
Operations
코틀린은 숫자 타입에 대해 +, -, *, /, % 산술 연산을 지원한다. 각 타입의 적절한 클래스 멤버로 선언되어 있다.
코틀린에서 두 정수의 나눗셈은 정수 타입을 리턴한다. 연산 결과가 소수인 경우에는 버림
부동 소수점 형식으로 반환하려면 인수 중 하나를 부동 소수점 형식으로 명시적으로 변환해야한다.
코틀린은 숫자 타입에 대해 +, -, *, /, % 산술 연산을 지원한다. 각 타입의 적절한 클래스 멤버로 선언되어 있다.
Bitwise operations
코틀린은 정수 숫자(Int, Long)에 대한 일련의 비트 연산을 제공한다. 2진 숫자 표현의 비트에서 바로 동작한다. 비트 연산은 infix 형태로 표현된다.
- shl (bits) - 부호를 유지하면서 shift left
- shr (bits) - 부호를 유지하면서 shift right
- ushr (bits) - 부호와 관계없이 shift right
- and (bits)
- or (bits)
- xor (bits)
- inv() - 비트 단위 반전 (not)
Floating-point numbers comparison
- 동등 비교 : a == b, 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 부동소수점 산술 표준을 따른다.
피연산자가 부동소수점형 숫자로 정적으로 형식화 되지 않은 경우(Any, Comparable<>, 형식 매개변수), Float 및 compareTo에 대해 equals 및 compareTo 구현을 사용한다.
- NaN == NaN → true (NaN은 자기 자신과 동일한 것으로 간주된다.)
- NaN은 양의 무한대(POSITIVE_INFINITY)를 포함한 다른 어떤 원소보다 크다.
- - 0.0 < 0.0
Unsigned integers
코틀린은 부호가 없는 정수 타입을 지원한다.
- UByte : unsigned 8비트 정수 (0 .. 255)
- UShort : unsigned 16비트 정수 (0 .. 65535)
- UInt : unsigned 32비트 정수 (0 .. \(2^32\) -1)
- ULong : unsigned 64비트 정수 (0 .. \(2^64\) -1)
Unsigned 타입은 signed 타입의 대부분 작업을 지원한다. 하지만!! Unsigned 타입에서 signed 타입으로, 혹은 그 반대로 변환되지 않는다!
Unsigned arrays and ranges (not stable)
⚠️ Unsigned array와 연산은 베타 버전입니다. (2022.02.05)
언제든지 호환되지 않게 변경될 수 있으며, 사용하려면 아직 안정적이지 않아 not stable 경고가 뜨는데 이를 제거하기 위해서는 @ExperimentalUnsignedTypes 애노테이션을 사용해야한다.
- UByteArray
- UShortArray
- UIntArray
- ULongArray
일반 signed 정수 타입 배열과 마찬가지로 boxing 오버헤드 없이 Array 클래스와 유사한 API를 제공한다.
Unsigned Range (stable)
range와 progressions는 UIntRange, UIntProgression, ULongRangem ULongProgression 클래스에서 UInt형과 ULong형에 대해 지원한다.
Unsigned Literal
부호 없는 정수를 쉽게 사용하기 위해서 이를 명시적으로 나타낼 수 있다. 정확한 유형은 예상 유형에 따라 결정된다. Type이 제공되지 않으면 컴파일러가 리터럴의 크기에 따라 UInt 또는 ULong으로 추론한다. uL이나 UL 를 접미사에 붙여서 사용할 수도 있다.
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
Booleans
true, false 두가지 값을 가질 수 있다. 논리 연산 ||(or), &&(and), !(not)을 제공한다.
정수 타입과 마찬가지로 JVM 플랫폼 위에서 boolean 기본형으로 저장된다. nullable한 참조가 있을 경우 (Boolean?)을 사용한다.
Character
문자 하나를 표현한다. 문자 리터럴은 따옴표(')로 감싸서 나타낸다. 특정 문자는 (\)문자로 이스케이프 (\t, \b, \n, \r, \', \", \\, \$)해서 사용한다. 다른 문자를 인코딩 하려면 유니코드 이스케이프 시퀀스 구문 'uFF00'을 사용한다.
String
문자열은 코틀린에서 String 타입으로 표현된다. 일반적으로 문자열 값은 문자들의 시퀀스 이며 쌍따옴표(")로 감싸서 나타낸다. 문자열의 각 문자에 대해서 인덱스 연산으로 접근이 가능하며 향상된 for문을 사용하여 각 문자에 대해 반복할 수 있다.
문자열은 불변객체이다. 문자열을 초기화하면 값을 변경하거나 새 값을 할당할 수 없다. 문자열을 변환하는 모든 작업은 결과를 새 String 객체에 반환하고 원래 문자열은 변경되지 않은 상태로 유지한다.
문자열을 연결하려면 + 연산자를 사용한다. 이는 표현식의 첫번째 요소가 문자열일때, 다른 유형의 값과 문자열을 연결하는 데에도 작동한다. 대부분의 경우 문자열 연결보다 문자열 템플릿 또는 원시 문자열을 사용하는 것이 좋다.
String literals
- 이스케이프된 문자를 포함할 수 있는 이스케이프된 문자열
- 열 개행(Enter) 및 임의의 텍스트를 포함할 수 있는 원시 문자열 (쌍따옴표 3개로 묶어서 사용하며, 이스케이프된 문자를 사용하지 않고 개행 문자를 포함한 어떤 문자들도 포함할 수 있다.
val s = "Hello, world!\n"
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() 함수에 parameter로 전달할 수 있다.
String templates
문자열 리터럴은 템플릿 표현식을 포함할 수 있다. 템플릿 표현식은 달러 기호($)로 시작하고 다음 이름 중 하나로 구성된다.
fun main() {
val s = "abc"
println("$s.length is ${s.length}") // prints "abc.length is 3"
}
문자열 템플릿은 원시 문자열이나 이스케이프된 문자열 모두에서 사용이 가능하다.
val price = """
${'$'}_9.99
"""
Arrays
코틀린의 배열은 Array 클래스로 표현된다. Array 클래스에는 연산자 오버로딩 규칙과 배열 크기 속성에 의해 인덱스 연산자 []로 변환되는 get, set함수등 여러 유용한 멤버 함수가 있다.
// Array 클래스 내부 구현
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 요소로 채워진 지정된 크기의 배열을 만들 수 있다.
또한, 배열 크기를 취하는 Array 생성자와 인덱스가 지정된 배열 요소의 값을 반환하는 함수를 사용해서 배열을 생성할 수 있다.
코틀린의 배열은 불변이다. 즉, 코틀린은 Array<String>을 Array<Any>에 할당하여 런타임 실패 가능성을 방지할 수 없다.
val intArr = arrayOf(1, 2, 3) // [1, 2, 3]
val arr = arrayOfNulls(5) // [null, null, null, null, null]
val zeroToFour = Array(5) { i -> i } // [0, 1, 2, 3, 4]
val emptyArr = emptyArray<Int>() // []
Primitive type arrays
코틀린에서는 ByteArray, IntArray, DoubleArray 와 같은 기본형의 배열 클래스(박싱 오버헤드 없음)도 존재한다.
// IntArray
val arr: IntArray = intArrayOf(1, 2, 3, 4, 5) // [1, 2, 3, 4, 5]
val arr = IntArray(5) // [0, 0, 0, 0, 0]
val arr = IntArray(5) { 7 } // [7, 7, 7, 7, 7]
val arr = IntArray(5) { it } // [0, 1, 2, 3, 4]
Array 클래스와 상속 관계가 아니지만, 메소드 및 속성이 동일하며, 각각의 팩토리 함수도 제공한다.
var arr1 = arrayOf(1, 2, 3, 4, 5) // [1, 2, 3, 4, 5]
var arr2 = intArrayOf(1, 2, 3, 4, 5) // [1, 2 ,3, 4 ,5]
arr1 = arr2 // Type Mismatch Error