본문 바로가기

프로그래밍/Android

[Kotlin] Basic types (기본 타입)

Kotlin에서는 모든 변수에 대해 멤버 함수와 속성을 호출할 수 있다는 점에서 모든 것이 오브젝트이다. 일부 타입은 특별한 내부 표현을 가질 수 있다. 예를 들어 숫자, 문자 및 Boolean은 런타임에 기본 값으로 표시될 수 있지만 사용자에게는 일반 클래스처럼 보인다. 이 섹션에서는 Kotlin에서 사용되는 기본 유형인 숫자, Boolean, 문자, 문자열, 배열에 대해 설명한다.

Numbers

Integer types

Kotlin은 숫자를 나타내는 내장된 타입들을 제공한다.
정수의 경우 크기가 다르므로 값 범위가 다른 네 가지 타입이 있다.

Type Size (bits) Min value Max value
Byte 8 -128 127
Short 16 -32768 32767
Int 32 -2,147,483,648(-2^31) 2,147,483,647(2^31 - 1)
Long 64 -9,223,372,036,854,775,808(-2^63) 9,223,372,036,854,775,807(2^63 - 1)

Int의 최대값을 초과하지 않는 정수 값으로 초기화된 모든 변수에는 Int 타입이 유추된다. 초기 값이 이 값을 초과하면 타입은 Long이다. 값을 Long으로 명시적으로 지정하려면 값에 접미사 L을 추가하면 된다.

val one = 1 // Int
val threeBillion = 3000000000 // Long
val oneLong = 1L // Long
val oneByte: Byte = 1

Floating-point types

실수의 경우 Kotlin은 부동 소수점 유형 FloatDouble을 제공한다. IEEE 754 표준에 따르면 부동 소수점 타입은 소수점 자릿수, 즉 저장할 수 있는 소수점 자릿수에 따라 다르다. FloatIEEE 754 _single precision_를 반영하는 반면 Double은 _double precision_를 제공한다.

Type Size (bits) Significant bits Exponent bits Decimal digits
Float 32 24 8 6-7
Double 64 53 11 15-16

소수 부분이 있는 숫자로 DoubleFloat 변수를 초기화할 수 있다. 정수 부분과 마침표(.)로 구분된다. 소수로 초기화된 변수의 경우 컴파일러는 Double 타입을 유추한다.

val pi = 3.14 // Double
// val one: Double = 1 // Error: type mismatch
val oneDouble = 1.0 // Double

값에 대한 Float 타입을 명시적으로 지정하려면 접미사 f 또는 F를 추가하시오. 이러한 값에 6-7자리 이상의 십진수가 포함되어 있으면 반올림된다.

val e = 2.7182818284 // Double
val eFloat = 2.7182818284f // Float, actual value is 2.7182817

일부 다른 언어와 달리 Kotlin에는 숫자에 대한 암시적 확대 변환이 없다. 예를 들어 Double 파라미터가 있는 함수는 Double 값에서만 호출할 수 있고 Float, Int 또는 기타 숫자 값에서는 호출할 수 없다.

fun main() {
    fun printDouble(d: Double) { print(d) }

    val i = 1
    val d = 1.0
    val f = 1.0f

    printDouble(d)
//    printDouble(i) // Error: Type mismatch
//    printDouble(f) // Error: Type mismatch
}

숫자 값을 다른 타입으로 변환하려면 명시적 변환을 사용하시오.

Literal constants

정수 값에는 다음과 같은 종류의 리터럴 상수가 있다.

  • Decimals: 123
    • Longs are tagged by a capital L: 123L
  • Hexadecimals: 0x0F
  • Binaries: 0b00001011

8진수 리터럴은 지원되지 않는다.

Kotlin은 부동 소수점 숫자에 대한 conventional notation도 지원한다.

  • Doubles by default: 123.5, 123.5e10
  • Floats are tagged by f or F: 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 플랫폼에서 숫자는 int, double 등의 기본 타입으로 저장된다. 예외는 Int?와 같은 nullable 숫자 참조를 만드는 경우 또는 제네릭을 사용하는 경우이다. 이러한 경우 숫자는 Java 클래스 Integer, Double 등으로 박싱되어 표시된다.

동일한 숫자에 대한 nullable 참조는 다른 오브젝트일 수 있다.

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) // false

a에 대한 모든 nullable 참조는 JVM이 -128127 사이의 Integer 에 적용하는 메모리 최적화 때문에 실제로 동일한 오브젝트이다. b 참조에는 적용되지 않으므로 서로 다른 오브젝트이다.

반면에 값은 여전히 동일하다.

val b: Int = 10000
println(b == b) // Prints 'true'
val boxedB: Int? = b
val anotherBoxedB: Int? = b
println(boxedB == anotherBoxedB) // Prints 'true'

// 첨언하면 == 값 비교 === 주소 비교

명시적 변환

다른 참조로 인해 더 작은 타입은 더 큰 타입의 _subtypes_이 아니다. 그렇다면 다음과 같은 문제가 발생한다.

// 이것은 가상의 코드다. 실제로 컴파일되지 않음
val a: Int? = 1 // A boxed Int (java.lang.Integer)
val b: Long? = a // 암시적 변환은 a boxed Long (java.lang.Long)
print(b == a) // 놀랍게도, false가 출력이 된다. Long의 equals() 는 다른 값 또한 Long으로 확인하기 때문이다.

결과적으로 더 작은 타입은 더 큰 타입으로 암시적으로 변환되지 않는다. 즉, Byte 타입의 값을 Int 변수에 할당하려면 명시적 변환이 필요하다.

val b: Byte = 1 // 리터널은 정적으로 확인된다.
// val i: Int = b // ERROR
val i1: Int = b.toInt()

모든 숫자 타입은 다른 타입으로의 변환을 지원한다.

  • toByte(): Byte
  • toShort(): Short
  • toInt(): Int
  • toLong(): Long
  • toFloat(): Float
  • toDouble(): Double
  • toChar(): Char

대부분의 경우 타입이 컨텍스트에서 유추되고 산술 연산이 적절한 변환을 위해 오버로드되기 때문에 명시적 변환이 필요하지 않다.
예를 들면 다음과 같다.

val l = 1L + 3 // Long + Int => Long

Operations

Kotlin은 숫자에 대한 표준 산술 연산들(+, -, *, /, %)을 지원한다. 그들은 적절한 클래스의 멤버로 선언된다.

println(1 + 2)
println(2_500_000_000L - 1L)
println(3.14 * 2.71)
println(10.0 / 3)

Division of integers

정수 숫자 사이의 나눗셈은 항상 정수를 반환한다. 모든 소수 부분은 삭제된다.

val x = 5 / 2
//println(x == 2.5) // ERROR: Operator '==' cannot be applied to 'Int' and 'Double'
println(x == 2)

이는 두 정수 타입 간의 나눗셈에 해당된다.

val x = 5L / 2
println(x == 2L)

부동 소수점 형식을 반환하려면 인수 중 하나를 부동 소수점 타입으로 명시적으로 변환하시오.

val x = 5 / 2.toDouble()
println(x == 2.5)

Bitwise operations

Kotlin은 정수에 대한 비트 연산들을 제공한다. 숫자 표현의 비트를 사용하여 이진 수준에서 직접 작동한다. 비트 연산은 중위 형식으로 호출할 수 있는 함수로 표시된다. IntLong에만 적용할 수 있다.

val x = (1 shl 2) and 0x000FF000

다음은 비트 연산의 전체 목록이다.

  • shl(bits) – 부호 있는 왼쪽 시프트
  • shr(bits) – 부호 있는 오른쪽 시프트
  • ushr(bits) – 부호 없는 왼쪽 시프트
  • and(bits) – and 연산
  • or(bits) – or 연산
  • xor(bits) – xor 연산
  • inv() – inversion 연산

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 또는 nullable 대응하는 것으로 알려진 경우(타입이 선언되거나 유추되거나 스마트 캐스트의 결과) 숫자 및 이들이 형성하는 범위에 대한 연산은 IEEE 754 Standard for Floating-Point Arithmetic를 따른다.

그러나 일반적인 사용 사례를 지원하고 전체 순서를 제공하기 위해 피연산자가 부동 소수점 숫자(예: Any, Comparable<...>, 타입 파라미터)로 정적으로 타입이 지정되지 않은 경우 연산은 Float 및 compareTo에 대해 equals 및 compareTo 구현을 사용한다. 표준에 동의하지 않는 Double은 다음을 수행한다.

  • NaN은 자신과 동일한 것으로 간주된다.
  • NaN은 POSITIVE_INFINITY를 포함한 다른 요소보다 큰 것으로 간주된다.
  • -0.0은 0.0보다 작은 것으로 간주된다

Unsigned integers

정수 유형 외에도 Kotlin은 unsigned 정수에 대해 다음 타입을 제공한다.

  • UByte: 0에서 255 사이의 부호 없는 8비트 정수
  • UShort: 0에서 65535 사이의 부호 없는 16비트 정수
  • UInt: 0에서 2^32 - 1 사이의 부호 없는 32비트 정수
  • ULong: 0 ~ 2^64 - 1 범위의 부호 없는 64비트 정수

unsigned 타입은 signed 타입의 대부분의 연산을 지원한다.

unsigned 타입에서 signed 타입으로(또는 그 반대로) 타입을 변경하는 것은 이진 호환되지 않는 변경이다.

Unsigned arrays and ranges

unsigned 타입의 배열 및 이에 대한 연산은 베타 버전이다. 언제든지 호환되지 않게 변경될 수 있다. opt-in 이 필요하다(아래 세부 정보 참조).

기본형과 마찬가지로 각 unsigned 타입에는 해당 타입의 배열을 나타내는 타입이 있다.

  • UByteArray: unsigned byte 배열
  • UShortArray: unsigned short 배열 타입
  • UIntArray: unsigned int 배열 타입
  • ULongArray: unsigned long 배열 타입

signed 정수 배열과 마찬가지로 boxing 오버헤드 없이 Array 클래스와 유사한 API를 제공한다.

unsigned 배열을 사용하면 이 기능이 아직 안정적이지 않다는 경고가 표시된다. 경고를 제거하려면 @ExperimentalUnsignedTypes 어노테이션을 사용해라. 클라이언트가 API 사용에 명시적으로 옵트인해야 하는지 결정하는 것은 프로그래머에게 달려 있지만, unsigned 배열은 안정적인 기능이 아니므로 이를 사용하는 API는 언어 변경으로 인해 손상될 수 있다.

범위 및 진행은 UIntRange, UIntProgression, ULongRangeULongProgression 클래스에서 UIntULong에 대해 지원된다. unsigned 정수 타입과 함께 이러한 클래스는 안정적이다.

Literals

unsigned 정수를 더 쉽게 사용할 수 있도록 Kotlin은 특정 unsigned 타입을 나타내는 접미사로 정수 리터럴에 태그를 지정하는 기능을 제공한다.(Float 또는 Long과 유사)

uU는 unsigned 리터럴에 태그를 지정한다. 정확한 타입은 예상 타입에 따라 결정된다. 예상되는 타입이 제공되지 않으면 컴파일러는 리터럴의 크기에 따라 UInt 또는 ULong을 사용한다.

val b: UByte = 1u  // UByte, 예상 타입 제공
val s: UShort = 1u // UShort, 예상 타입 제공
val l: ULong = 1u  // ULong, 예상 타입 제공

val a1 = 42u // UInt: 예상되는 타입이 제공되지 않음, UInt의 상수가 맞다.
val a2 = 0xFFFF_FFFF_FFFFu // ULong: 예상되는 타입이 제공되지 않음, ULong의 상수가 아니다.

uLUL은 명시적으로 리터럴을 unsigned long으로 태그 지정한다.

val a = 1UL // 예상 유형이 제공되지 않고 상수가 UInt의 범위에 있지만 ULong이다.

Booleans

Boolean 타입은 truefalse의 두 가지 값을 가질 수 있는 boolean 오브젝트를 나타낸다.
Boolean에는 nullable 대응하는 Boolean?이 있다. null 값도 있다.

boolean 에 대한 기본 연산은 다음과 같다.

  • || - 분리 (logical OR)
  • && - 병합 (logical AND)
  • ! - 부정 (logical NOT)

||와 && 의 작업은 lazy 하다.

val myTrue: Boolean = true
val myFalse: Boolean = false
val boolNull: Boolean? = null

println(myTrue || myFalse)    // true
println(myTrue && myFalse)    // false
println(!myTrue)            // false

JVM에서 boolean 오브젝트에 대한 nullable 참조는 숫자와 유사하게 박싱된다.

Characters

문자는 Char 타입으로 표시된다. 문자 리터럴은 작은 따옴표로 묶으면 된다: '1'.

특수 문자는 이스케이프 백슬래시 \로 시작한다. 지원되는 이스케이프 시퀀스는 \t, \b, \n, \r, \', \", \\, \$ 이다.

다른 문자를 인코딩하려면 유니코드 이스케이프 시퀀스 구문 '\uFF00'을 사용한다.

val aChar: Char = 'a'

println(aChar)
println('\n') //prints an extra newline character
println('\uFF00')

문자 변수의 값이 숫자인 경우 digitToInt() 함수를 사용하여 명시적으로 Int 숫자로 변환할 수 있다.

JVM에서 숫자와 마찬가지로 nullable 참조가 필요할 때 문자가 박싱된다. 항등성은 boxing 작업에 의해 보존되지 않는다.

Strings

Kotlin의 문자열은 String 타입으로 표시된다. 일반적으로 문자열 값은 큰따옴표(")로 묶인 일련의 문자이다.

val str = "abcd 123"

문자열의 요소는 인덱싱 작업인 s[i]를 통해 액세스할 수 있는 문자이다. for 루프를 사용하여 다음 문자를 반복할 수 있다.

for (c in str) {
    println(c)
}

문자열은 변경할 수 없다. 문자열을 초기화하면 값을 변경하거나 새 값을 할당할 수 없다. 문자열을 변환하는 모든 작업은 결과를 새 String 오브젝트에 반환하고 원래 문자열은 변경되지 않은 상태로 유지된다.

val str = "abcd"
println(str.uppercase()) // 새로운 String 오브젝트 생성 및 출력
println(str) // 원래 문자열은 그대로 유지

문자열을 연결하려면 + 연산자를 사용한다. 이는 표현식의 첫 번째 요소가 문자열인 한 다른 타입의 값과 문자열을 연결하는 데에도 작동한다.

val s = "abc" + 1
println(s + "def")

대부분의 경우 문자열 연결보다 문자열 템플릿 또는 원시 문자열을 사용하는 것이 좋다.

String literals

Kotlin에는 두 가지 유형의 문자열 리터럴이 있다.

  • escaped 문자를 포함할 수 있는 escaped 문자열
  • 줄 바꿈과 임의의 텍스트를 포함할 수 있는 원시 문자열

다음은 escaped 문자열의 예이다.

val s = "Hello, world!\\n"  

escape는 백슬래시()를 사용하는 일반적인 방식으로 수행된다.

원시 문자열은 삼중 따옴표(""")로 구분되며 escape를 포함하지 않으며 줄 바꿈 및 기타 문자를 포함할 수 있다.

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(">")과 같은 매개변수로 전달할 수 있다.

String templates

문자열 리터럴은 템플릿 표현식을 포함할 수 있다. 템플릿 표현식은 달러 기호($)로 시작한다.

val i = 10  
println("i = $i") // "i = 10" 이 출력된다.

혹은 중괄호로 묶인 표현식으로 구성된다.

val s = "abc"  
println("$s.length is ${s.length}") // "abc.length is 3" 이 출력된다.

원시 문자열과 escape 문자열 모두 템플릿을 사용할 수 있다. 식별자의 시작으로 허용되는 기호 앞에 $ 문자를 원시 문자열(백슬래시 escape를 지원하지 않음)에 삽입하려면 다음 구문을 사용하시오.

val price = """  
${'$'}\_9.99  
"""

Arrays

Kotlin의 배열은 Array 클래스로 표현된다. 다른 유용한 멤버 함수와 함께 연산자 오버로딩 규칙과 크기 속성에 의해 []로 바뀌는 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() 함수를 사용하고 항목 값을 배열에 전달하여 arrayOf(1, 2, 3)가 배열 [1, 2, 3]을 생성하도록 한다. 또는 arrayOfNulls() 함수를 사용하여 null 요소로 채워진 지정된 크기의 배열을 만들 수 있다.

또 다른 옵션은 배열 크기를 취하는 Array 생성자와 인덱스가 지정된 배열 요소의 값을 반환하는 함수를 사용하는 것이다.

// 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()에 대한 호출을 나타낸다.

Kotlin의 배열은 _불변_이다. 즉, Kotlin에서는 Array<String>을 Array<Any>에 할당할 수 없으므로 런타임 오류가 발생할 수 있다(그러나 Array <out Any>를 사용할 수 있다.)

Primitive type arrays

Kotlin에는 ByteArray, ShortArray, IntArray 등 boxing 오버헤드 없이 기본 타입의 배열을 나타내는 클래스도 있다. 이러한 클래스는 Array 클래스와 상속 관계가 없지만 메서드 및 속성 집합이 동일하다. 각각에는 해당 팩토리 기능도 있다.

val x: IntArray = intArrayOf(1, 2, 3)
x[0] = x[1] + x[2]
// 값이 [0, 0, 0, 0, 0]인 크기 5의 int 배열
val arr = IntArray(5)

// 예) 상수로 배열의 값 초기화
// 값이 [42, 42, 42, 42, 42]인 크기가 5인 int 배열
val arr = IntArray(5) { 42 }

// 예) 람다를 사용하여 배열의 값 초기화
// 값이 [0, 1, 2, 3, 4]인 크기 5의 int 배열(인덱스 값으로 초기화된 값))
var arr = IntArray(5) { it * 1 }

원문

 

Basic types | Kotlin

 

kotlinlang.org