1. 코틀린 언어 소개
코틀린은 젯브레인스(JetBrains) 에서 오픈소스 그룹을 만들어 개발한 프로그래밍 언어입니다. 코틀린은 2011년 처음 공개되었으며 2017년 구글에서 안드로이드 공식 언어로 지정하면서 유명해졌습니다. '자바'가 인도네시아 섬 이름을 따와서 사용했듯이 자바를 대체할 목적으로 만든 '코틀린'도 러시아 섬 이름에서 유래했습니다.
코틀린으로 안드로이드 앱을 개발할 수 있는 것은 자바의 가상 머신인 JVM에 기반을 둔 언어이기 때문입니다. 즉, 코틀린으로 작성한 프로그램은 JVM에서 실행할 수 있습니다.
코틀린은 분명 자바와는 다른 언어입니다. 자바는 확장자로 .java를 사용하지만 코틀린은 .kt 를 사용합니다. 하지만 코틀린 컴파일러(kotlinc)가 .kt 파일을 컴파일하면 자바 바이트 코드가 만들어집니다. 즉, 개발자는 자바와 다른 코틀린으로 코드를 작성하지만 컴파일하면 자바 클래스가 만들어지고 이를 JVM이 실행합니다. 이 때문에 코틀린이 자바를 대체할 목적으로 만든 언어라고 소개하는 것이죠.
그러면 코틀린으로 앱을 개발하면 자바보다 어떤 이점이 있을까요?
- 표현력과 간결함
- 코틀린의 최신 언어 기법을 이용하면 훨씬 간결한 구문으로 프로그램을 작성할 수 있습니다. 같은 로직을 자바와 코틀린으로 각각 작성해 보면 코틀린 코드가 훨씬 간결하다는 것을 알 수 있죠.
- 안전한 코드
- 코틀린은 널 안전성(null safety)을 지원하는 언어입니다. 객체지향 프로그래밍에서 객체는 널 상태일 수 있으며 이때 런타임 오류인 널 포인트 예외가 발생할 수 있습니다. 코틀린에서는 변수를 '널 허용'과 '널 불허용'으로 구분해서 선언합니다. 이로써 널과 관련된 여러 부분을 컴파일러가 해결해 줍니다. 그만큼 개발자가 작성하는 코드가 널 안정성을 확보할 수 있습니다.
- 상호 운용성
- 코틀린은 자바와 100% 호환됩니다. 따라서 코틀린으로 프로그램을 작성할 때 자바 클래스나 라이브러리를 얼마든지 활용할 수 있습니다. 하나의 앱을 개발할 때 자바와 코틀린을 혼용해도 됩니다. 이미 자바로 개발한 앱을 유지보수할 때 추가하는 코드만 코틀린으로 작성할 수도 있죠.
- 구조화 동시성
- 코틀린 언어가 제공하는 코루틴(coroutines)이라는 기법을 이용하면 비동기 프로그래밍을 간소화 할 수 있습니다. 네트워크 연동이나 데이터베이스 갱신과 같은 작업을 할 때 이용하면 프로그램을 조금 더 간단하게 그리고 효율적으로 작성할 수 있습니다.
코틀린 파일 구성
코틀린 파일의 확장자는 '.kt'입니다. 다음은 User.kt라는 이름의 코틀린 파일의 소스 예제입니다.
package com.example.test
import java.text.SimpleDateFormat
import java.util.*
var data = 10
fun formatData(date: Date): String {
val sdformat = SimpleDateFormat("yyyy-mm-dd")
return sdformat.format(date)
}
class User {
var name = "hello"
fun sayHello() {
println("name : $name")
}
}
위 소스를 컴파일하면 클래스는 각각의 클래스 파일로 만들어집니다. 즉, User.kt 파일에 User라는 클래스를 선언했으므로 컴파일하면 User.class 파일이 만들어집니다. 그런데 코틀린 소스 파일의 최상위에 선언한 변수와 함수는 자동으로 '파일명+Kt'라는 이름의 클래스에 포함됩니다.
<UserKt.class>
data
formatDate(date: Date)
<User.class>
class User
즉, 위의 코드에서 data 변수와 formatDate() 함수는 컴파일한 후 UserKt.class 파일에 포함됩니다. 따라서 최상위에 선언한 변수, 함수를 자바에서 이용할 때는 자동으로 만들어지는 클래스를 이용해 접근하면 됩니다.
public class TestJava {
public static void main(String[] args) {
UserKt.setData(20);
UserKt.formatDate(new Date());
User user = new User();
user.sayHello();
}
}
2. 변수와 함수
변수 선언하기
코틀린에서 변수는 val, var 키워드로 선언합니다. val은 value의 줄임말로 초깃값이 할당되면 바꿀 수 없는 변수를 선언할 때 사용합니다. var는 variable의 줄임말로 초깃값이 할당된 후에도 값을 바꿀 수 있는 변수를 선언할 때 사용합니다.
val(혹은 var) 변수명: 타입 = 값
다음은 val과 var 키워드의 차이를 보여주는 예입니다.
val data1 = 10
var data2 = 10
fun main() {
data1 = 20 // 오류!
data2 = 20 // 성공!
}
변수명 뒤에는 콜론(:)을 추가해 타입을 명시할 수 있으며, 대입하는 값에 따라 타입을 유추(타입 추론)할 수 있을 때는 생략할 수 있습니다.
val data1: Int = 10
val data2 = 10
data1은 명시적으로 Int 타입을 선언한 것이며 data2는 대입한 값이 10이므로 타입을 명시하지 않아도 자동으로 Int 타입이 됩니다.
최상위에 선언한 변수나 클래스의 멤버 변수는 선언과 동시에 초깃값을 할당해야 하며, 함수 내부에 선언한 변수는 선언과 동시에 초깃값을 할당하지 않아도 됩니다. 물론 변수를 이용하려면 값을 할당하고 이용해야 합니다.
val data1: Int // 오류!
val data2 = 10 // 성공!
fun someFun() {
val data3: Int
println("data3": $data3) // 오류!
data3 = 10
println("data3": $data3) // 성공!
}
class User {
val data4: Int // 오류!
val data5: Int = 10 // 성공!
}
그런데 변수를 선언할 떄 초깃값을 할당할 수 없는 경우가 있습니다. 이때는 값을 이후에 할당할 것이라고 컴파일러에게 알려 주어야 합니다. 이때 lateinit나 lazy 키워드를 이용합니다. lateinit 키워드는 이후에 초깃값을 할당할 것임을 명시적으로 선언합니다.
lateinit로 선언한 변수는 선언과 동시에 초깃값을 할당하지 않아도 되지만 모든 유형의 변수 선언에 사용할 수는 없으며 다음 2가지 규칙을 따라야 합니다.
- lateinit은 var 키워드로 선언한 변수에만 사용할 수 있습니다.
- Int, Long, Short, Double, Float, Boolean, Byte 타입에는 사용할 수 없습니다.
Lazy 키워드는 변수 선언문 뒤에 by lazy { } 형식으로 선언하며, 소스에서 변수가 최초로 이용되는 순간 중괄호로 묶은 부분이 자동으로 실행되어 그 결과값이 변수의 초깃값으로 할당됩니다. lazy 문의 중괄호 부분을 여러 줄로 작성한다면 마지막 줄의 실행 결과가 변수의 초깃값이 됩니다.
val data4: Int by lazy {
println("in lazy......")
10
}
fun main() {
println("in main......")
println(data4 + 10)
println(data4 + 10)
}
<실행결과>
in main......
in lazy......
20
20
데이터 타입
이제 코틀린의 데이터 타입에 대해 알아보겠습니다. 그 전에 한 가지 알아둬야 할 부분은 코틀린의 모든 변수는 객체라는 것입니다. 따라서 코틀린의 모든 타입은 객체 타입입니다. 정수를 다루는 타입이 Int인데 Int는 원시 타입이 아니라 클래스입니다.
fun someFun() {
var data1: Int = 10
var data2: Int? = null // null 대입 가능
data1 = data1 + 10
data1 = data1.plus(10) // 객체의 메서드 이용 가능
}
다음은 코틀린에서 사용하는 타입을 정리한 내용입니다.
구분 | 타입 | 코드 예시 |
정수 | Int, Short, Long | val a1: Int = 123 val a2: Short = 123 val a3: Long = 10L |
실수 | Double, Float | val a1: Double = 10.0 val a2: Float = 10.0f |
이진수 | Byte | val a1: Byte = 0b00001011 |
불린 | Boolean | val a1: Boolean = true |
문자 | Char | val a: Char = 'a' |
문자열 | String | val str1 = "Hello \n World" val str2 = """ Hello World """ <String template> fun main() { fun sum(no: Int): Int { var sum = 0 for (i in 1..no) { sum += i } return sum } val name: String = "kkang" println("name : $name, sum : ${sum(10)}, plus : ${10 + 20}") } |
다음은 코틀린으로 개발하면서 알아두어야할 키워드에 관한 정리내용입니다.
구분 | 설명 | 코드 예시 |
Any | 모든 타입 가능 Any는 코틀린에서 최상위 클래스 모든 코틀린의 클래스는 Any의 하위 클래스 |
val data1: Any = 10 val data2: Any = "hello" class User val data3: Any = User() |
Unit | 반환문이 없는 함수 Unit 타입 변수는 Unit 객체만 대입가능 함수 반환문이 없음을 명시적으로 나타냄 |
val data1: Unit = Unit fun some(): Unit { println(10 + 20) } fun some() { println(10 + 20) } |
Nothing | null 이나 예외를 반환하는 함수 Nothing 타입 변수는 null 만 대입 가능 함수 반환은 하지만 의미 있는 값은 아니라는 의미 항상 null만 반환하는 함수나 예외를 던지는 함수의 반환 타입에 사용 |
val data1: Nothing? = null fun some1(): Nothing? { return null } fun some1(): Nothing { throw Exception() } |
? | 널 허용과 불허용 코틀린은 null 대입 변수와 null 대입 불가능 변수를 명확하게 구분 타입 뒤에 물음표를 추가하면 널 허용 타입 뒤에 물음표 없으면 널 불허용 |
var data1: Int = 10 data1 = null // 오류! var data2: Int? = 10 data2 = null // 성공! |
함수 선언하기
코틀린에서 함수를 선언하려면 fun 이라는 키워드를 이용합니다.
fun 함수명(매개변수명: 타입): 반환 타입 { ... }
함수에는 반환 타입을 선언할 수 있으며 생략하면 자동으로 Unit 타입이 적용됩니다.
함수의 매개변수에는 var나 val 키워드를 사용할 수 없습니다. val이 자동으로 적용되며 함수 안에서 매개변숫값을 변경할 수 없습니다.
함수의 매개변수에는 기본값을 선언할 수 있습니다. 만약 어떤 매개변수에 기본값을 선언했다면 호출할 때 인자를 전달하지 않아도 되며 이때 선언문에 명시한 기본값이 적용됩니다.
fun main() {
fun some(data1: Int, data2: Int = 10): Int {
return data1 * data2
}
println(some(10))
println(some(10, 20))
}
<실행결과>
100
200
어떤 함수의 매개변수가 여러 개면 호출할 때 전달한 인자를 순서대로 할당합니다.
fun some(data1: Int, data2: Int): Int {
return data1 * data2
}
println(some(10, 20))
그런데 아래처럼 매개변수명을 지정해서 호출할 수도 있습니다. 매개변수명을 지정하여 호출하는 것을 명명된 매개변수(named parameter)라고 하며 이렇게 하면 함수 선언문의 매개변수 순서에 맞춰 호출하지 않아도 됩니다.
some(data2 = 20, data1 = 10)
컬렉션 타입
컬렉션 타입이란 여러 개의 데이터를 표현하는 방법이며 Array, List, Set, Map이 있습니다.
- Array - 배열 표현
코틀린의 배열은 Array 클래스로 표현합니다. Array 클래스의 생성자에서 첫 번째 매개변수는 배열의 크기이며 두 번째 매개변수는 초깃값을 지정하는 함수입니다. 배열의 타입은 제네릭으로 표현합니다.
<init>(size: Int, init: (Int) -> T)
아래 코드는 0으로 초기화한 데이터를 3개 나열한 정수형 배열을 선언합니다.
val data1: Array<Int> = Array(3, { 0 })
배열의 데이터에 접근할 때는 대괄호([])를 이용해도 되고 set()이나 get() 함수를 이용할 수도 있습니다.
fun main() {
val data1: Array<Int> = Array(3, { 0 })
data1[0] = 10
data2[1] = 20
data1.set(2, 30) // 배열에서 2번째 데이터를 30으로 설정
println(
"""
array size : ${data1.size}
array data : ${data1[0]}, ${data1[1]}, ${data1.get(2)}
"""
)
}
만약 배열의 데이터가 기초 타입이라면 Array를 사용하지 않고 각 기초 타입의 배열을 나타내는 클래스를 이용할 수도 있습니다.
- BooleanArray
- ByteArray
- CharArray
- DoubleArray
- FloatArray
- IntArray
- LongArray
- ShortArray
val data1: IntArray = IntArray(3, { 0 })
val data2: BooleanArray = BooleanArray(3, { false })
또한 arrayOf()라는 함수를 이용하면 배열을 선언할 때 값을 할당할 수도 있습니다.
// 크기가 3인 Int 배열을 선언하고 10, 20, 30으로 할당
val data1 = arrayOf<Int>(10, 20, 30)
arrayOf() 함수도 기초 타입을 대상으로 함수가 제공됩니다.
- booleanArrayOf()
- byteArrayOf()
- charArrayOf()
- doubleArrayOf()
- floatArrayOf()
- intArrayOf()
- longArrayOf()
- shortArrayOf()
val data1 = intArrayOf(10, 20, 30)
val data2 = booleanArrayOf(true, true, true)
- List, Set, Map
List, Set, Map은 Collection 인터페이스를 타입으로 표현한 클래스이며 통털어 컬렉션 타입 클래스라고 합니다.
컬렉션 | 설명 | 타입 | 함수 | 특징 |
List | 순서가 있는 데이터 집합으로 데이터의 중복을 허용합니다. | List | listOf() | 불변 |
MutableList | mutableListOf() | 가변 | ||
Set | 순서가 없으며 데이터의 중복을 허용하지 않습니다. | Set | setOf() | 불변 |
MutableSet | mutableSetOf() | 가변 | ||
Map | 키와 값으로 이루어진 데이터 집합으로 순서가 없으며 키의 중복은 허용하지 않습니다. | Map | mapOf() | 불변 |
MutableMap | mutableMapOf() | 가변 |
Collection 타입의 클래스는 가변(mutable)클래스와 불변(immutable)클래스로 나뉩니다. 불변 클래스는 초기에 데이터를 대입하면 더 이상 변경할 수 없는 타입입니다. 그런데 가변 클래스는 초깃값을 대입한 이후에도 데이터를 추가하거나 변경할 수 있습니다.
<리스트 사용 예>
var list = listOf<Int>(10, 20, 30)
<가변 리스트 사용 예>
var mutableList = mutableListOf<Int>(10, 20, 30)
mutableList.add(3, 40)
mutableList.add(0, 50)
<리스트 데이터>
50, 20, 30, 40
Map 객체는 키와 값으로 이루어진 데이터의 집합입니다. Map 객체의 키와 같은 Pair 객체를 이용할 수도 있고 '키 to 값' 형태로 사용할 수도 있습니다.
var map = mapOf<String, String>(Pair("one", "hello"), "two" to "world")
3. 조건문과 반복문
조건문 if~else와 표현식
if ~ else 문은 대부분의 프로그래밍 언어에서 제공하는 조건문과 차이가 없습니다. 그런데 코틀린에서 if~else는 표현식으로도 사용할 수 있습니다. 표현식이란 결과값을 반환하는 계산식을 말합니다.
fun main() {
var data = 10
val result = if (data > 0) {
println("data > 0")
true // 참일 때 반환값
} else {
println("data <= 0")
false // 거짓일 때 반환값
}
println(result)
}
<실행결과>
data > 0
true
if ~ else 문을 표현식으로 사용하려면 else 를 생략할 수 없습니다. 항상 else 구문이 있어야 합니다. 그리고 if ~ else 표현식이 반환하는 결과값은 각 영역의 마지막 줄에 해당합니다.
조건문 when
코틀린은 if ~ else 구문 말고도 when 이라는 조건문을 사용할 수 있습니다.
fun main() {
var data = 10
when (data) {
10 -> println("data is 10")
20 -> println("data is 20")
else -> {
println("data is not valid data")
}
}
}
when 키워드 다음의 소괄호 () 안에 넣은 데이터가 조건이 되고 이 값에 따라 각 구문을 실행합니다. when 조건문으로 정수가 아닌 다른 타입의 데이터를 지정할 수도 있습니다.
fun main() {
var data = "hello"
when (data) {
"hello" -> println("data is hello")
"world" -> println("data is world")
else -> {
println("data is not valid data")
}
}
}
fun main() {
var data: Any = 10
when (data) {
is String -> println("data is String") // data가 문자열 타입이면
20, 30 -> println("data is 20 or 30") // data가 20 또는 30이면
in 1..10 -> println("data is 1..10") // data가 1~10의 값이면
else -> println("data is not valid data")
}
}
위 소스에서 is는 타입을 확인하는 연산자이며 in은 범위 지정 연산자입니다. is String은 데이터가 String 타입이면 참이고, in 1..10은 데이터가 1부터 10까지 범위이면 참입니다.
또한 when 문을 이용하면서 데이터를 명시하지 않고 조건만 명시할 수도 있습니다.
fun main() {
var data = 10
when {
data <= 0 -> println("data is <= 0")
data > 100 -> println("data is > 100")
else -> println("data is valid")
}
}
when은 if 문과 마찬가지로 표현식으로도 사용할 수 있습니다. 즉, when 문의 실행 결과를 반환할 수 있습니다. when 문을 표현식으로 사용할 때는 else 문을 생략할 수 없습니다.
fun main() {
var data = 10
val result = when {
data <= 0 -> "data is <= 0"
data > 100 -> "data is > 100"
else -> "data is valid"
}
println(result)
}
반복문 for
코틀린에서는 주로 in 범위 지정 연산자를 이용해서 for문을 작성합니다.
fun main() {
var sum: Int = 0
for (i in 1..10) {
sum += i;
}
println(sum)
}
for 문의 조건은 다양하게 작성할 수 있습니다.
- for (i in 1..10) { ... } : 1부터 10까지 1씩 증가
- for (i in 1 until 10) { ... } : 1부터 9까지 1씩 증가(10은 미포함)
- for (i in 2..10 step 2) { ... } : 2부터 10까지 2씩 증가
- for (i in 10 downTo 1) { ... } : 10부터 1까지 1씩 감소
증감 조건을 숫자로 명시하지 않고 컬렉션 타입의 데이터 개수만큼 반복하게 할 수도 있습니다.
fun main() {
var data = arrayOf<Int>(10, 20, 30)
for (i in data.indices) {
print(data[i])
if (i !== data.size - 1) print(",")
}
}
배열의 크기만큼 for 문을 반복하게 작성한 소스입니다. indices는 컬렉션 타입의 인덱스값을 의미하므로 for 문을 반복하면서 0, 1, 2 값을 i에 대입합니다. for문을 반복하면서 인덱스와 실제 데이터를 함께 가져오려면 withIndex() 함수를 이용합니다.
fun main() {
var data = arrayOf<Int>(10, 20, 30)
for ((index, value) in data.withIndex()) {
print(value)
if (index !== data.size - 1) print(",")
}
}
'Android' 카테고리의 다른 글
[깡쌤의 안드로이드 프로그래밍 with 코틀린][3. 앱의 기본 기능 구현하기] 6. 뷰를 이용한 화면 구성 (0) | 2024.12.25 |
---|---|
[깡쌤의 안드로이드 프로그래밍 with 코틀린][2. 코틀린 이해하기] 5. 코틀린의 유용한 기법 (0) | 2024.12.25 |
[깡쌤의 안드로이드 프로그래밍 with 코틀린][2. 코틀린 이해하기] 4. 코틀린 객체지향 프로그래밍 (0) | 2024.12.25 |
[깡쌤의 안드로이드 프로그래밍 with 코틀린][1. 안드로이드 앱 개발 준비하기] 2. 안드로이드 앱의 기본 구조 (0) | 2024.12.25 |
[깡쌤의 안드로이드 프로그래밍 with 코틀린][1. 안드로이드 앱 개발 준비하기] 1. 개발 환경 준비하기 (0) | 2024.12.25 |