Post

Kotlin 문법 - 데이터 타입

Kotlin Logo

개요

Kotlin에 어떠한 기본 데이터 타입들이 있는지 알아보기 전에, Kotlin 데이터 타입이 가지는 특징에 대해서 먼저 알아보겠습니다.


Kotlin 데이터 타입 특징

Kotlin 데이터 타입의 특징은 크게 다섯 가지가 있습니다.

1) 모든 것이 객체

2) Null 안정성

3) 타입 추론

4) 원시 타입과 객체 타입 동일

5) Immutable, Mutable 타입

이 중 가장 큰 특징은 모든 것이 객체라는 특징이며 이는 기본 데이터 타입에 대해서도 동일하게 적용됩니다.

모든 것이 객체

Java에서는 int, double와 같은 기본 데이터 타입과 Integer, Double와 같은 참조 데이터 타입이 있었습니다.

1
2
3
4
5
6
7
8
9
10
11
int i1 = 1;
double d1 = 1.1;

System.out.println(i1.toString()) // 컴파일 에러
System.out.println(d1.toString()) // 컴파일 에러

Integer i2 = new Integer(1);
Double d2 = new Double(1.1);

System.out.println(i2.toString()) // 컴파일 성공
System.out.println(d2.toString()) // 컴파일 성공

위 코드를 보면 기본 데이터 타입에서는 메소드에 접근할 수 없고 참조 데이터 타입에서만 메소드에 접근할 수 있는 것을 확인할 수 있습니다.

하지만 앞서 말했듯이 Kotlin은 모든 것이 객체로 취급되므로 기본 데이터 타입도 객체입니다. 따라서 메서드나 프로퍼티에 직접 접근할 수 있습니다.

1
2
3
4
5
val i1: Int = 1
val d1: Double = 1.1

println(i1.toString()) // 컴파일 성공
println(d1.toString()) // 컴파일 성공

위 코드에서 볼 수 있듯이 기본 데이터 타입이 객체일 경우, 모든 데이터 타입이 공통된 인터페이스(메소드, 프로퍼티)를 가지고 있게 되어 코드를 좀 더 간결하게 만들 수 있고 타입에 대한 처리를 일관성 있게 할 수 있습니다.

이러한 접근 방식은 함수형 프로그래밍과도 잘 어울립니다.

예를 들어 Java에서는 Stream API를 이용하여 Collection에 대한 연산을 수행하는데, 기본 데이터 타입에 대해서는 별도의 방식을 사용해야 합니다.

1
2
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
int sum = numbers.stream().mapToInt(Integer::intValue).sum();

위 코드에서 mapToInt() 메소드는 Integer 객체를 기본 데이터 타입인 int로 변환하기 위해 사용되었습니다. 반면에 Kotlin에서는 기본 데이터 타입도 객체로 취급되므로 Java보다 훨씬 간결하게 동일한 연산을 수행할 수 있습니다.

1
2
val numbers = listOf(1, 2, 3, 4, 5)
val sum = numbers.sum()

위 코드에서 볼 수 있듯이 numbers 객체에서 Kotlin 표준 라이브러리 함수인 sum()을 바로 사용할 수 있습니다.

Null 안정성

Kotlin에서는 변수가 null 값을 가질 수 있음을 반드시 명시적으로 선언해야 합니다. ? 연산자를 이용하여 변수가 nullable임을 명시할 수 있고 ? 연산자가 없는 변수에 null 값을 넣을 경우 컴파일 에러가 발생하게 됩니다. 이를 통해 NullPointerException을 방지할 수 있습니다.

관련 연산자로는 ?, ?., ?:, !! 등이 있으며 각각의 역할은 다음과 같습니다.

? 연산자

nullable 가능한 변수를 선언할 때 해당 변수의 데이터 타입 뒤에 추가하는 연산자입니다.

1
2
3
4
5
var strNotNullable: String = "abc"
strNotNullable = null      // 컴파일 에러

var strNullable: String? = "abc"
strNullable = null         // 컴파일 성공

?. 연산자

객체의 메소드를 호출하거나 멤버 변수에 접근할 때, 해당 객체가 null이 아니라면 해당 메소드를 호출하거나 멤버 변수에 직접 접근합니다. 만약 null일 경우 이후의 메소드 호출 및 멤버 변수 접근을 모두 무시하고 null을 반환하며 이를 통해 NullPointerException을 방지합니다.

1
2
var strNullable: String? = null
println(strNullable?.length)    // null 출력

?: 연산자

null 대신에 사용할 기본 값을 지정할 수 있습니다. 접근하려는 객체가 null일 경우에 반환할 기본값을 지정하는데 사용됩니다.

1
2
var strNullable: String? = null
println(strNullable?.length ?: 0) // 0 출력

!! 연산자

개발자가 해당 변수가 null이 아니라고 확인할 때 사용합니다. 만약, 해당 변수가 null일 경우 즉시 NullPointerException을 발생시키고 아닐 경우 변수의 값을 그대로 반환합니다. 따라서 !! 연산자는 항상 주의해서 사용해야 합니다.

1
2
var strNullable: String? = null
println(strNullable!!.length) // NullPointerException 발생

타입 추론

Kotlin에서는 변수의 타입을 컴파일러가 자동으로 추론하므로, 변수 선언 시 타입을 명시적으로 지정하지 않아도 됩니다. 이를 통해 코드를 간결하게 작성할 수 있고 생산성을 향상 시킬 수 있습니다.

하지만 과도한 타입 추론은 코드가 길어질 경우 가독성을 해칠 수 있기 때문에 이 점에 유의해야 합니다. 또한, 함수의 반환 타입은 명시적으로 지정하는 것이 좋습니다. 그렇지 않을 경우 반환 타입이 무엇인지 파악하기 어려워 추후 코드를 이해하고 유지보수하는 데 많은 어려움이 발생할 수 있습니다.

아래는 타입을 지정하지 않은 자동 추론에 대한 예시 코드입니다.

1
2
val text = "Hello, World!"
val num = 123

위 코드에서는 컴파일러가 textString 타입으로, numInt 타입으로 자동으로 판단합니다.

원시 타입과 객체 타입 동일

Kotlin은 자동 박싱과 언방식을 지원합니다. 즉, 필요에 따라 원시 타입과 객체 타입 사이를 자동으로 변환합니다. 이를 통해 원시 타입과 객체 타입 사이의 차이를 없애고 일관된 방식으로 타입을 사용할 수 있다는 장점이 있습니다.

1
2
val i: Int = 10
println(i.toString())

위 코드에서 볼 수 있듯이 원시 타입인 i가 객체 타입처럼 취급되기 때문에 toString()이라는 메소드 호출이 가능한 것을 확인할 수 있습니다.

추가적으로, Kotlin의 원시 타입은 객체로 취급될 수 있지만 성능 저하를 방지하기 위해 컴파일 타임에서 원시 타입으로 자동 변환을 수행합니다. 이로 인해, 원시 타입을 사용할 수 있는 모든 곳에서 객체 타입을 사용할 수 있으며, 반대의 경우도 마찬가지입니다. 이러한 기능은 코드를 더욱 간결하고 일관성 있게 만드는데 도움이 됩니다.

Immutable, Mutable 타입

Kotlin에서는 데이터의 불변성을 지원하기 위해 valvar라는 두 가지 타입을 제공합니다. val은 한번 할당된 값을 변경할 수 없는 불변(Immutable) 변수이며, var은 값을 변경할 수 있는 변경 가능(Mutable) 변수입니다. val은 Java에서 final 키워드와 동일한 역할을 합니다. 이러한 기능은 함수 사이의 안전한 데이터 전달과 동시성 환경에서 안정성을 제공해주며, 변수의 사용의도를 명확히 알 수 있습니다.

1
2
3
4
5
val immutableVal = "Hello"
immutableVal = "World"      // 컴파일 에러

var mutableVar = "Hello"
mutableVar = "World"        // 컴파일 성공

위 코드에서 val로 선언된 변수는 변경이 불가능하기 때문에 수정 시 컴파일 에러가 발생하고, var로 선언된 변수는 수정 가능하기 때문에 mutableVarWorld로 변경 가능한 것을 확인할 수 있습니다.

이어서 Kotlin에는 어떠한 데이터 타입이 존재하는지에 대해서 알아보겠습니다.


기본 데이터 타입

Kotlin은 Java와 매우 유사한 데이터 타입을 제공합니다. 이 데이터 타입을 카테고리로 나눠보면 다음과 같습니다.

카테고리타입
IntegersByte, Short, Int, Long
Unsigned IntegersUByte, UShort, UInt, ULong
Floating-Point NumbersFloat, Double
BooleansBoolean
CharactersChar
StringsString

Integers

Kotlin에서 숫자를 표현하는데 사용됩니다.

타입설명범위
Byte8-bit 부호 있는 정수-128 ~ 127
Short16-bit 부호 있는 정수-32,768 ~ 32,767
Int32-bit 부호 있는 정수-2,147,483,648 ~ 2,147,483,647
Long64-bit 부호 있는 정수-9,223,372,036,854,775,808 ~ 9,223,372,036,854,775,807

Unsigned Integers

Kotlin에서 양의 숫자를 표현하는데 사용됩니다.

타입설명범위
UByte8-bit 부호 없는 정수0 ~ 255
UShort16-bit 부호 없는 정수0 ~ 65,535
UInt32-bit 부호 없는 정수0 ~ 4,294,967,295
ULong64-bit 부호 없는 정수0 ~ 18,446,744,073,709,551,615

Floating-Point Numbers

Kotlin에서 실수를 표현하는데 사용됩니다.

타입설명범위
Float32-bit 부동소수점 숫자1.4E-45 ~ 3.4028235E38
Double64-bit 부동소수점 숫자4.9E-324 ~ 1.7976931348623157E308

Booleans

Kotlin에서 참/거짓을 표현하는데 사용되는 타입으로 Boolean 타입이 존재합니다.

Characters

Kotlin에서 단일 문자를 표현하는데 사용되는 타입으로 Char 타입이 존재합니다.

Strings

Kotlin에서 문자열을 표현하는데 사용되는 타입으로 String 타입이 존재합니다.


다음은 각각의 기본 데이터 타입을 사용하는 예시입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
val byteVal: Byte = 127
val shortVal: Short = 32767
val intVal: Int = 2147483647
val longVal: Long = 9223372036854775807L

val uByteVal: UByte = 255u
val uShortVal: UShort = 65535u
val uIntVal: UInt = 4294967295u
val uLongVal: ULong = 18446744073709551615uL

val floatVal: Float = 3.4028235E38f
val doubleVal: Double = 1.7976931348623157E308

val booleanVal: Boolean = true

val charVal: Char = 'A'

val stringVal: String = "Hello World!"


기본 데이터 타입에서 제공하는 메소드

앞서 Kotlin 데이터 타입 특징 섹션에서 언급했듯이 각각의 데이터 타입이 객체이기 때문에 데이터 타입에서 다양한 메소드를 호출할 수 있습니다.

Int, Float, Double에서 제공하는 메소드

메소드설명
plus()숫자를 더합니다.
minus()숫자를 뺍니다.
times()숫자를 곱합니다.
div()숫자를 나눕니다.
rem()나머지를 계산합니다.
compareTo()다른 숫자와 비교합니다.
rangeTo()범위를 생성합니다.
toDouble(), toFloat(), toInt(), toLong()다른 숫자 타입으로 변환합니다.
1
2
3
4
5
6
7
8
9
10
11
val number1 = 10
val number2 = 20

println(number1.plus(number2))      // 출력: 30
println(number1.minus(number2))     // 출력: -10
println(number1.times(number2))     // 출력: 200
println(number1.div(number2))       // 출력: 0.5
println(number1.rem(number2))       // 출력: 10
println(number1.compareTo(number2)) // 출력: -1
println(number1.rangeTo(number2))   // 출력: 10..20
println(number1.toDouble())         // 출력: 10.0

Boolean에서 제공하는 메소드

메소드설명
and()논리곱(AND) 연산을 수행합니다.
or()논리합(OR) 연산을 수행합니다.
not()부정(NOT) 연산을 수행합니다.
1
2
3
4
5
6
val bool1 = true
val bool2 = false

println(bool1.and(bool2))           // 출력: false
println(bool1.or(bool2))            // 출력: true
println(bool1.not())                // 출력: false

Char에서 제공하는 메소드

메소드설명
plus()문자에 특정 숫자를 더하여 다음 문자를 얻습니다.
minus()문자에 특정 숫자를 빼서 이전 문자를 얻습니다.
isDigit(), isLetter()문자가 숫자나 문자인지 확인합니다.
toUpperCase(), toLowerCase()대문자 또는 소문자로 변환합니다.
1
2
3
4
5
6
7
8
val char1 = 'a'
val number1 = 1

println(char1.plus(number1))        // 출력: b
println(char1.minus(number1))       // 출력: ` 
println(char1.isDigit())            // 출력: false
println(char1.isLetter())           // 출력: true
println(char1.toUpperCase())        // 출력: A

String에서 제공하는 메소드

메소드설명
length문자열의 길이를 얻습니다.
get()특정 위치의 문자를 얻습니다.
substring()문자열의 부분 문자열을 얻습니다.
contains()문자열이 특정 문자열 또는 문자를 포함하는지 확인합니다.
startsWith(), endsWith()문자열이 특정 문자열로 시작하거나 끝나는지 확인합니다.
indexOf(), lastIndexOf()특정 문자열 또는 문자의 위치를 찾습니다.
split()문자열을 특정 문자열 또는 문자로 분할합니다.
replace()문자열에서 특정 문자열 또는 문자를 다른 문자열 또는 문자로 교체합니다.
toUpperCase(), toLowerCase()문자열을 대문자 또는 소문자로 변환합니다.
trim()문자열의 앞뒤 공백을 제거합니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
val string1 = "Hello, World!"

println(string1.length)                 // 출력: 13
println(string1.get(0))                 // 출력: H
println(string1.substring(0, 5))        // 출력: Hello
println(string1.contains("World"))      // 출력: true
println(string1.startsWith("Hello"))    // 출력: true
println(string1.endsWith("!"))          // 출력: true
println(string1.indexOf("World"))       // 출력: 7
println(string1.lastIndexOf("!"))       // 출력: 12
println(string1.split(","))             // 출력: [Hello,  World!]
println(string1.replace("Hello", "Hi")) // 출력: Hi, World!
println(string1.toUpperCase())          // 출력: HELLO, WORLD!
println(string1.toLowerCase())          // 출력: hello, world!
println(" Hello, World! ".trim())       // 출력: Hello, World!


마무리

Kotlin에 대한 추가적인 정보는 다음 링크를 참고하시면 되겠습니다.

Kotlin 공식 사이트

  • Kotlin 개요 및 문서와 튜토리얼 등 Kotlin과 관련된 다양한 정보

Kotlin Koans

  • Kotlin 기본 문법 및 기능을 배우기 위한 연습 문제 제공

Kotlin for Android Developers

  • Google에서 제공하는 Kotlin을 이용한 Android 개발 가이드
This post is licensed under CC BY 4.0 by the author.