코틀린을 우연히 다뤄봤는데 굉장히 재미있어서 이 재미를 공유하고 싶은 마음이 들었다. 그래서 코틀린을 기초부터 웹 개발까지 간략히 다루는 코틀린 0부터 웹개발까지 시리즈를 작성하려 한다. 개발 환경은 IntelliJ와 MacOS를 이용중이므로 이에 맞춰 작성된다.
Hello Kotlin
콘솔창에 Hello World를 출력하는 예제를 생성할 것이다. 필자는 아래와 같이 프로젝트를 생성했다.

Main.kt
파일을 보면, 아래와 같이 작성되어 있는 것을 볼 수 있다.
fun main(args: Array<String>) {
println("Hello World!")
// Try adding program arguments via Run/Debug configuration.
// Learn more about running applications: https://www.jetbrains.com/help/idea/running-applications.html.
println("Program arguments: ${args.joinToString()}")
}
- 1행: 프로그램의 진입점(entry point)인
main
함수의 선언부이다. - 2행: Hello World라는 문자열을 콘솔창에 출력시키는 코드이다.
- 4-5행: 주석. 아래에서 설명.
- 6행: 프로그램 인자(program arguments)를 출력해주는 코드이다.run Main.kt
$ ... MainKt arg1 arg2 arg3
outputHello World! Program arguments: arg1 arg2 arg3
💡 이후 작성되는 예제 코드는 모두 main
메소드 내부에 작성되며, 해당 메소드는 생략하도록 하겠다.
주석
- 한 줄 주석 :
//
기호를 사용하여 한 줄 주석을 작성한다.//
뒤의 모든 내용은 주석으로 처리되며, 해당 줄 끝까지만 유효한다. 한 줄 주석은 코드 설명, 임시 코드 비활성화 또는 개발자의 메모 등을 작성하는 데 사용된다. - 여러 줄 주석 :
/*
로 시작하고*/
로 끝나는 여러 줄 주석을 사용하여 여러 줄에 걸쳐 주석을 작성할 수 있다. 여러 줄 주석은 주로 긴 주석 블록, 함수 또는 클래스에 대한 설명, 라이선스 정보 등을 작성하는 데 사용된다. - KDoc 주석 :
/**
로 시작하고*/
로 끝나는 주석으로 코드의 API 문서를 작성할 때 사용되며, 툴이나 IDE를 통해 추출하여 API 문서를 생성하는데 사용된다.
변수의 선언
코틀린에서는 var
과 val
2가지 방법으로 변수를 선언한다.
var
: mutable(가변) 변수. 언제든지 값을 변경할 수 있다.var age = 30 age = 31
val
: immutable(불변) 변수. 한 번 값을 할당하면 변경할 수 없다.그러나val age = 30 age = 31 // ❌ Error
val
로 선언한 변수라도, 참조값만 immutable 하고 객체는 mutable 하기 때문에 사용에 주의해야 한다.val sb = StringBuilder("hello") sb.append("Object") sb.append("is") sb.append("mutable")
변수의 기본 자료형
코틀린에서 변수는 원시 타입(primitive type)이나 래퍼 타입(wrapper type)을 구분하지 않는다.
다시 말해, 자바처럼 int
(원시 타입)나 Integer
(래퍼 타입)로 구분하는 것이 아닌, Int
하나만 사용한다.
그런데 이처럼 모든 변수를 객체로 처리하면 성능상 비효율적 일 수 있지 않나 생각이 들 수 있다.
사실 그렇지 않은데, 코틀린은 바이트 코드로 컴파일 될 때 자동으로 최적화되어 원시 타입이 필요한 자리에는 원시 타입을, 래퍼 타입이 필요한 자리에는 래퍼 타입으로 변환해준다.
이제 어떤 타입의 자료형이 있는지 살펴보자.
정수형
자료형 | 크기 (bits) | 최소값 | 최대값 |
---|---|---|---|
Byte | 8 | -128 | 8 |
Short | 16 | -32768 | 16 |
Int | 24 | -2,147,483,648 (-231) | 2,147,483,647 (231 - 1) |
Long | 32 | -9,223,372,036,854,775,808 (-263) | 9,223,372,036,854,775,807 (263 - 1) |
- 명시적으로 타입을 지정하지 않으면, 타입을 자동으로 추론한다.
Int
범위를 초과하지 않으면Int
이며, 초과하면Long
타입이 된다.
Long
타입을 명시적으로 지정하려면 접미사 L을 추가한다.
val one = 1 // Int
val threeBillion = 3000000000 // Long
val oneLong = 1L // Long
val oneByte: Byte = 1
실수형

자료형 | 크기 (bits) | 가수 비트 (Significant bits) | 지수 비트 (Exponent bits) | 소수점 자리수 (Decimal digits) |
---|---|---|---|---|
Float | 32 | 24 | 8 | 6-7 |
Double | 64 | 53 | 11 | 15-16 |
- 명시적으로 타입을 지정하지 않으면,
Double
타입으로 추론된다. Float
타입을 명시적으로 지정하려면 접미사 f를 추가한다.- 소수점 이하 자릿수가 6~7자리 이상 포함되면 반올림된다.
val e = 2.7182818284 // Double val eFloat = 2.7182818284f // Float, actual value is 2.7182817
- 다른 언어와 달리 Kotlin에서는 숫자에 대한 암시적 확대 변환이 없다.
예를 들어 매개 변수 타입이
Double
인 함수는Double
값에 대해서만 호출할 수 있으며Float
,Int
또는 기타 숫자 값을 넘겨서 호출할 수 없다.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
논리형
Boolean
타입(논리형)은 참 또는 거짓을 가질 수 있는 데이터 타입이다.
Boolean
에 내장된 연산은 다음과 같다.
||
- 합연산(논리적 OR)&&
- 곱연산(논리적 AND)!
- 부정(논리적 NOT)
val myTrue: Boolean = true
val myFalse: Boolean = false
val boolNull: Boolean? = null
println(myTrue || myFalse) // true
println(myTrue && myFalse) // false
println(!myTrue) // false
문자형
코틀린은 문자를 UTF-16 BE(Big Endian)으로 관리하므로 16 bits 메모리 공간을 차지한다.
Char
타입으로 표현되며, 문자 리터럴은 작은 따옴표로 묶어 사용한다.
💡 리터럴(literal)이란 그 자체로 의미를 갖는, 코드 상에 표현된 값을 말한다. 그 예로 정수 리터럴 42는 코드에서 나타나는 숫자 42 자체를 나타내며, 어떠한 추가적인 처리나 연산 없이도 그 자체로 숫자 42의 의미를 갖는다. 쉽게 말해서 숫자, 문자, 참-거짓, 배열 등을 코드 상에 표현한 것을 의미한다.
특수문자를 표시하기 위해서 이스케이프 시퀀스(escape sequences)를 지원한다.
이스케이프 시퀀스는 백슬래시(\
)와 특정 문자 조합으로 이루어져있으며, 문자열 안에서도 사용 가능하다.
\t
: 탭\b
: 백스페이스\n
: 새 줄(LF)\r
: 캐리지 리턴(CR) - 커서를 현재 줄의 시작 부분으로 이동합니다.\'
: 작은따옴표\'
: 큰따옴표\\
: 백슬래시\$
: 달러 기호
유니코드 이스케이프 시퀀스 구문을 사용해서 다른 문자를 인코딩 할 수도 있다.
\u
로 시작하며, 그 뒤에 4자리 16진수 숫자를 조합해서 표현한다.
val aChar = '\u0061'
println(aChar) // a
println(aChar.code) // 97
문자열
String
타입은 문자열을 나타내는 자료형이며, 아래와 같이 큰 따옴표로 묶은 문자의 연속으로 표현된다.
val str = "a1b2"
문자열 요소는 for
루프를 통해서 반복할 수 있다.
for (c in str) {
println(c)
}
a
1
b
2
코틀린의 문자열은 자바에서의 문자열 특성을 그대로 물려받아 불변성을 띈다. 따라서 문자열을 초기화하면 값을 변경하거나 새 값을 할당할 수 없다. 또한 문자열을 변환하는 모든 연산은 기존 문자열은 그대로 두고 새 문자열을 반환한다.
var str = "abcd 123"
println(str.uppercase()) // ABCD 123
println(str.replace("abcd","efgh")) // efgh 123
println(str) // abcd 123
문자열을 연결하기 위해 +
연산자를 사용하며 첫번째 요소가 문자열이라면 다른 타입의 값과 연결하더라도 작동한다.
val s = "abc" + 1
println(s + "def")
문자열 리터럴에 대해서도 알아보자. 코틀린에서는 두 타입의 문자열 리터럴을 제공한다.
- 이스케이프 문자열
이전에 문자 파트에서 언급했었는데, 이스케이프 문자를 문자열에서도 사용할 수 있다.
val s = "Hello, world!\n"
- 멀티라인 문자열
멀티라인 문자열은 개행과 임의의 텍스트를 포함할 수 있다. 이 문자열은 삼중 큰 따옴표("""
)를 통해 사용되며, 입력되는 그대로 출력하므로 이스케이프 문자를 사용할 수 없다.
fun main(args: Array<String>) {
val text = """
for (c in "foo")
print(c)
"""
println(text)
}
for (c in "foo")
print(c)
멀티라인 문자열에서 선행 공백을 없애려면 trimMargin()
을 사용하면 된다.
fun main(args: Array<String>) {
val text = """
|Tell me and I forget.
|Teach me and I remember.
|Involve me and I learn.
|(Benjamin Franklin)
""".trimMargin()
println(text)
}
Tell me and I forget.
Teach me and I remember.
Involve me and I learn.
(Benjamin Franklin)
기본적으로 파이프 문자 |
가 여백 접두사(margin prefix)로 사용되지만, trimMargin(">")
과 같이 파라미터로 특정 문자를 넘겨서 지정할 수 있다.
문자열 리터럴은 템플릿 표현식(template expressions)을 포함할 수 있다.
템플릿 표현식은 코드 조각으로, 실행된 결과가 문자열에 연결(concatenated)된다.
달러 기호($
) 기호로 시작되며, 그 뒤 변수의 이름이 온다.
val i = 10
println("i = $i") // i = 10
또는 중괄호 안에 표현식을 넣을 수도 있다.
val s = "abc"
println("$s.length is ${s.length}") // abc.length is 3
문자열 템플릿은 멀티라인 문자열과 이스케이프 된 문자열 모두에 사용할 수 있는데, 이를 이용해서 멀티라인 문자열에 이스케이프 문자를 사용할 수도 있다.
// Prints \$_9.99 ❌
val priceIncorrect = """
\$_9.99
"""
// Prints $_9.99 ⭕️
val price = """
${'$'}_9.99
"""
- 멀티라인 문자열에서 달러 기호는 템플릿을 의미하므로 이스케이프 해야한다.
- 그러나 백슬래쉬를 이용한 이스케이프 문자는 사용할 수 없다.
- 이때 중괄호로 묶어, 표현식으로써 달러 문자를 사용할 수 있다.
배열
코틀린에서 배열은 Array
클래스로 표현된다.
이 클래스는 내부적으로 인덱싱 연산자([]
)로 변환되는 get()
및 set()
함수와 기타 유용한 멤버 함수가 있다.
배열을 생성할 때에는 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) }
코틀린에서 배열은 무공변성(invariant)인데, 쉽게 말해 서로 다른 타입 간의 상속 관계를 고려하지 않고 완전히 독립적인 타입으로 다루는 것을 말한다.
즉 코틀린에서는 Array<String>
을 Array<Any>
에 할당할 수 없으므로 런타임 오류 가능성을 방지할 수 있다.
이번에는 이 정도로만 이해하도록 하고 공변성(covariant), 반공변성(contravariant), 무공변성은 다른 글에서 다루도록 하곘다.
💡 Any 타입은 최상위 타입으로 모든 타입은 암묵적으로 Any 타입을 상속한다.
앞서 코틀린은 원시 타입과 래퍼 타입을 구분하지 않는다고 언급했다.
따라서 크기가 큰 배열 사용시 박싱 오버헤드가 발생할 수도 있는데, 이를 피하기 위해 ByteArray
, ShortArray
, IntArray
등과 같은 원시 타입 배열을 제공한다.
이 클래스들은 Array
와는 상속관계가 아니지만 동일한 메소드를 갖는다.
또 각각에 해당하는 팩토리 함수도 있다.
val x: IntArray = intArrayOf(1, 2, 3)
x[0] = x[1] + x[2]
// Array of int of size 5 with values [0, 0, 0, 0, 0]
val arr = IntArray(5)
// Example of initializing the values in the array with a constant
// Array of int of size 5 with values [42, 42, 42, 42, 42]
val arr = IntArray(5) { 42 }
// Example of initializing the values in the array using a lambda
// Array of int of size 5 with values [0, 1, 2, 3, 4] (values initialized to their index value)
var arr = IntArray(5) { it * 1 }
💡 박싱 오버헤드(boxing overhead)란, 원시 자료형 값을 객체로 감싸는 과정으로 인해 발생하는 성능 저하를 말한다.
형 변환 함수
기본 자료형들은 자료형 간의 형 변환을 지원하기 위해 형 변환 함수를 제공한다.
형 변환 함수는 to
뒤에 변환될 자료형의 이름을 갖고 있으며 목록은 다음과 같다.
toByte()
: 다른 자료형을 바이트로 변환한다.toShort()
: 다른 자료형을 짧은 Short 타입으로 변환한다.toInt()
: 다른 자료형을 Int 타입으로 변환한다.toLong()
: 다른 자료형을 Long 타입으로 변환한다.toFloat()
: 다른 자료형을 Float 타입으로 변환한다.toDouble()
: 다른 자료형을 Double 타입으로 변환한다.toChar()
: 다른 자료형을 Char 타입으로 변환한다.
이러한 형 변환 함수들은 데이터 유실 없이 값을 변환할 수 있는 경우에 사용된다. 그러나 변환하려는 값이 목표 자료형으로 완벽하게 매핑될 수 없는 경우 예외가 발생할 수 있으므로, 안전한 형 변환이 가능한지 확인이 필요하다는 점을 주의해야 한다.