해당 게시글은 인프런의 코틀린 문법 총 정리 강의를 들으면서 작성 되었습니다.
해당 강의 중 맨 마지막 섹션인 코루틴 섹션은 내용을 추가로 조사하여 작성 하였음을 알립니다.
컴파일 상수
const val num = 20
fun main() {
println("Hello, World")
}
main() 함수 상단부에 작성된 const val은 main() 함수보다 우선해서 컴파일되어 성능에 유리한 이점을 가져올 수 있다.
문자열에서 첫 번째 문자 다루기
fun main() {
val name = "abc"
println("${name[0].uppercase()}")
}
name[0]와 같이 문자열 접근시 문자열 변수에서 첫 번째 문자를 접근할 수 있다.
min, max
초기 버전의 코틀린에서는 자바 코드의 Math.max 함수를 사용하였지만, 최근에는 코틀린 코드로 작성된 min, max 함수를 사용하면 된다.
import kotlin.math.max
import kotlin.math.min
fun main() {
val i = 10
val j = 20
println(max(i, j))
println(min(i, j))
}
import 구문에 kotlin.math.*가 포함돼 있음을 체크하자.
Random
import kotlin.random.Random
fun main() {
val randomNumber = Random.nextInt(0, 100) // 0 ~ 99
val randomDouble = Random.nextDouble(0.0, 1.0) // 0.0 ~ 0.9
}
Scanner
값을 입력받는 Scanner 클래스이다.
import java.util.Scanner
fun main() {
val reader = Scanner(System.`in`)
reader.nextInt()
}
코틀린에서 in 키워드는 예약어라서 자동으로 백틱(`)이 붙게된다.
if, when 제어문
코틀린에서는 3항 연사자가 따로 없다. if 또는 when 제어문을 사용하면 된다.
fun main() {
val i = 5
// when으로 변경 가능
// 3항 연산자를 따로 사용하지 않는다.
val result = if (i > 10) {
"10 보다 크다"
} else if (i > 5) {
"5 보다 크다"
} else {
"!!!"
}
val result2 = when {
i > 10 -> "10 보다 크다"
i > 5 -> "5 보다 크다"
else -> "!!!"
}
println(result)
println(result2)
}
두개의 코드 블록 모두 동일한 코드이다. 마지막 줄의 코드가 리턴되기 때문에 각각 result, result2 변수에 값이 담기게 된다.
for, forEach
리스트의 각 아이템들을 출력시키는 다양한 방법이다. 정통적으로 사용되는 for (var i = 0; i < items.length; i++)과 같은 형태는 지양한다.
더 직관적이면서 다양한 문법들이 지원되기 때문이다. 그럼에도 불구하고 정통적인 반복문 형태의 포맷을 찾는다면 예시의 맨 마지막 형태가 정통적인 형태와 가장 유사하다.
fun main() {
val items = listOf(1, 2, 3, 4, 5)
for (item in items) {
print(item)
}
items.forEach {
println(it)
}
items.forEach { item ->
println(item)
}
items.forEach(::println)
println()
for (i in 0..(items.size - 1)) {
print(items[i])
}
}
List
fun main() {
val items = listOf(1, 2, 3, 4, 5) // 변경안됨
val mItems = mutableListOf(1, 2, 3, 4, 5)
mItems.add(6)
mItems.remove(3)
}
Array
fun main() {
val items = arrayOf(1, 2, 3) // 배열은 실질적으로 잘 사용하지 않고, List를 사용하면 된다.
println(items.size)
items[0] = 2
try {
val item = items[4]
} catch (e: Exception) {
println(e.message)
}
}
배열(Array)은 실질적으로 잘 사용하지 않고, List를 사용하면 된다.
배열과 리스트의 차이
- 배열은 고정된 크기를 가지며, 인덱스를 통해 각 요소에 직접 접근하고 변경할 수 있습니다. 배열의 크기는 생성 시에 결정되며 이후에는 변경할 수 없습니다.
- 성능의 관점에서 배열은 메모리에서 연속된 블록을 사용하기 때문에 성능이 약간 더 좋을 수 있습니다. 그러나 이러한 차이는 대부분의 애플리케이션에서 큰 영향을 미치지 않습니다.
- 리스트는 더 많은 기능과 유연성을 제공하지만, 성능 측면에서 배열보다 약간 느릴 수 있습니다. 특히, MutableList는 내부적으로 배열을 사용하기 때문에 성능 차이가 크지 않습니다.
- 대부분의 경우 리스트가 더 사용하기 편리하고(map, filter, reduce), Kotlin의 컬렉션 API와 더 잘 통합됩니다.
Null Safety
코틀린에서 nullable 타입을 표현하기 위해서는 String?과 같이 물음표를 붙여준다.
다만, Nullable 타입은 명시된 타입을 바로 할당할 수 없기 때문에 ?.let { it } 구문을 사용해 코드를 작성하는 것이 관례이다.
그 밖에 Null이 아님을 강제하는 !! 문법을 사용할 수 있지만, 이는 개발자에 실수가 될 수 있기 때문에 지양하도록 하자.
fun main() {
var nameNullable: String? = null
var name: String = ""
nameNullable?.let { // null이 아니라면, 블록을 실행하자.
name = it
}
}
함수 작성하기
파일에 작성된 함수들을 Top-Level 함수라 한다. 어느 파일에서나 사용가능하다.
아래의 sum, sum2 함수들을 호출하는 문법들은 모두 유효한 문법들이다.
fun main() {
println(sum(1, 2))
println(sum2(a = 10, b = 20))
println(sum2(b = 30, a = 10))
}
// Top-Level 함수, 어느 파일에서나 사용가능하다.
fun sum(a: Int, b: Int): Int {
return a + b
}
fun sum2(a: Int, b: Int, c: Int = 0) = a + b + c
클래스, Data 클래스
fun main() {
val jhon = Person("Jhon Smith", 28, "private name")
val jhon2 = Person("Jhon Smith", 28, "private name")
println(jhon) // Person@65b54208
println(jhon2) // Person@1be6fc3
println(jhon2 == jhon2) // false
val jhon3 = DataPerson("a", 1)
val jhon4 = DataPerson("a", 1)
println(jhon3 == jhon4) // `data class`는 주소 참조가 아닌 값 참조를 하기 때문에 `true`가 된다.
jhon.some()
println(jhon.hobby) // 취미: 농구
}
class Person(
private val name: String,
private val age: Int,
private val name2: String,
) {
var hobby = "축구"
private set // 외부에서 set이 불가능해 짐
get() = "취미: $field"
init {
// 초기화 구문, 객체 생성시 실행된다.
println("My name is $name and I am $age")
}
fun some() {
hobby = "농구"
}
}
data class DataPerson(
val name: String,
val age: Int,
)
상속을 위한 open 그리고 interface
- 일반 클래스를 상속 가능하게 하려면
open 키워드를 붙여야 한다.
- 추상 클래스에서 작성된 메소드 또한 오버라이드가 가능하게 하려면
open 키워드를 붙여야 한다.
interface Drawable {
fun draw()
}
abstract class Animal {
open fun move() {
println("move")
}
}
class Dog: Animal(), Drawable {
override fun move() {
println("살금")
}
override fun draw() {
TODO("Not yet implemented")
}
}
class Cat: Animal(), Drawable {
override fun draw() {
TODO("Not yet implemented")
}
}
타입 체크 is
위의 작성된 예시에서 타입을 체크 하려면 다음과 같이 작성해 볼 수 있다.
fun main() {
val dog: Animal = Dog()
val cat = Cat()
if (dog is Dog) {
println("멍멍이")
}
cat as Animal // type casting
if (dog is Animal) {
println("Animal")
}
}
Generic 클래스 생성
fun main() {
val box = Box(10)
val box2 = Box("asdf")
println(box.value)
println(box2.value)
}
class Box<T>(val value: T)
고차함수
fun main() {
myFunc(10) { // Lambda 식
println("Hello World")
}
}
// 콜백함수(고차함수)
fun myFunc(a: Int, callback: () -> Unit) {
println("함수 시작")
callback()
println("함수 끝")
}
코루틴(coroutine)
코루틴?
- 코루틴은 비동기 프로그래밍을 쉽게 할 수 있도록 도와주는 프로그래밍 구조이다.
- 쉽게 말해, 코루틴은 멈췄다가 나중에 다시 시작할 수 있는 함수이다.
- 코루틴을 사용하면 코드가 멈추지 않고 다른 작업을 할 수 있다.
코루틴으로 가능한 것들
- 메인 스레드가 멈추지 않음: 네트워크 요청이나 파일 읽기 같은 시간이 오래 걸리는 작업을 하면서도 메인 스레드가 멈추지 않아서 UI가 끊기지 않는다.
- 간편한 비동기 코드 작성: 콜백이나 복잡한 스레드 관리를 할 필요 없이, 동기 코드처럼 읽기 쉬운 비동기 코드를 작성할 수 있다.
- 효율적인 자원 사용: 필요할 때만 코드를 실행하므로, 자원을 효율적으로 사용할 수 있다.
코루틴의 작동 방식
- 시작:
suspend 함수로 표시된 코루틴 블록을 시작한다.
- 멈춤:
suspend 키워드가 있는 부분에서 멈춘다. 예를 들어, 네트워크 요청을 기다리는 동안 다른 작업을 한다.
- 재개: 요청이 완료되면 멈췄던 지점부터 다시 시작한다.
import kotlinx.coroutines.*
fun main() {
GlobalScope.launch {
val result = fetchData()
println("Data: $result")
}
println("Waiting for data...")
Thread.sleep(2000) // 메인 스레드가 종료되지 않도록 잠시 대기
}
suspend fun fetchData(): String {
delay(1000) // 네트워크 요청 시뮬레이션
return "Hello, Coroutine!"
}
이 예제에서는 fetchData 함수가 suspend로 표시되어 있고, 이 함수는 delay를 통해 잠시 멈췄다가 1초 후 다시 실행된다. 메인 스레드는 멈추지 않고 "Waiting for data..."를 출력하고, 이후 fetchData의 결과가 출력된다.