TIL 15일차 - kotlin 문법 #4 (24.03.19.)

남형주·2024년 3월 19일

TIL

목록 보기
15/35

오늘은 5주차 문법을 복습했다. 헷갈리는 것들이 너무나 많아서 구글 검색을 해가며 하다보니 시간이 금방 지나가버렸다. 배운 내용을 간단히 정리하겠다.

kotlin 문법 심화 (5주차 내용)


1. 자료형 변환

일반 자료형은 to자료형() 메소드로 간단하게 바꿀 수 있다.
객체 자료형은 상속관계에서 변환할 수 있다.
자식클래스를 부모클래스의 자료형으로 객체 생성하는 것을 '업캐스팅'이라고 하고, 그 반대의 경우를 '다운캐스팅'이라고 한다. as 클래스명()을 통해 활용할 수 있다. 아래의 간단한 예시를 통해 알 수 있다.

fun main() {
	println("몇 마리를 생성하시겠습니까?")
	var count = readLine()!!.toInt()
	var birds = mutableListOf<Bird>()

	for(idx in 0..count-1) {
    	println("조류의 이름을 입력해주세요")
    	var name = readLine()!!
   	 	birds.add(Sparrow(name) as Bird)
	}
	println("============조류 생성완료============")
	for(bird in birds) {
    	bird.fly()
	}
}

open class Bird(name: String) {
	var name: String

	init {
    this.name = name
	}

	fun fly() {
    	println("${name}이름의 조류가 날아요~")
		}
	}

class Sparrow(name: String): Bird(name) {
}

2. Scope functions - scope functions

자기 자신의 객체를 전달하는 함수들이라고 생각하면 편하다. 명시적으로 수신객체 자체를 람다로 전달하는 방법과 수신객체를 람다의 파라미터로 전달하는 방법으로 나뉜다.

// T는 수신객체, block: 내부는 람다함수의 소스코드
// 수신객체 자체를 람다의 수신객체로 전달하는 방법
public inline fun <T, R> T.run(block: T.() -> R): R
public inline fun <T> T.apply(block: T.() -> Unit): T
public inline fun <T, R> with(receiver: T, block: T.() -> R): R

// 수신객체를 람다의 파라미터로 전달
public inline fun <T> T.also(block: (T) -> Unit): T
public inline fun <T, R> T.let(block: (T) -> R): R
Scope에서 접근방식 thisScope에서 접근방식 it
블록 수행 결과를 반환run(null처리 가능), withlet
객체 자신을 반환applyalso

it과 this에 관한 것은 링크를 첨부한다. >> it, this


3. 확장 함수 - extension function

간단히 설명하자면 클래스 내부에서 만들지 않아도 실행할 수 있는 함수다. 원하는 메소드가 있지만 내가 설계한 클래스가 아닐때 외부에서 메소드를 관리한다. 그렇기 때문에 원본 클래스의 일관성을 유지할 수 있다는 장점이 있다.
단, public에만 접근할 수 있고 클래스의 멤버함수처럼 상속하는 건 불가능하다. 아직까지 어디에 활용해야할지 잘은 모르겠지만 언젠가 분명히 필요로 할 날이 올 것이다.


4. 쓰레드

프로그램에는 하나의 메인 쓰레드(실행흐름)라는 것이 존재한다. 하나의 메인 쓰레드는 메인함수(fun main())을 의미하는데, 별도의 자식 쓰레드를 생성해서 동시에 로직을 실행할 수 있다.
프로세스와 쓰레드
프로그램이 메모리에 올라가서 실행되는 것을 프로세스라고 하는데, 그 안에서의 더 작은 작업 단위를 쓰레드라고 한다. 쓰레드는 생성되서 수행할때 각 독립된 메모리 영역인 STACK을 갖게 된다. 쓰레드를 한 개 생성하면 스택메모리의 일정 영역을 차지하는 것이다.
이때 내부적으로 성능 향상을 위해 메인 메모리로부터 읽어온 값을 CPU 캐시에 저장하는데, 멀티쓰레드 애플리케이션에서는 각 쓰레드를 통해 CPU에 캐싱한 값이 상이할 수 있다. 여기서 변수 선언 시 @Volatile 을 붙이면 변수의 값이 메인 메모리에만 저장되며, 멀티 쓰레드 환경에서 메인 메모리의 값을 참조하기 때문에 변수값이 불일치하는 현상을 해결할 수 있다.
아래는 예시코드다. 두 쓰레드로 10개의 숫자 출력을 경쟁시킬 수 있다.

fun main() {
    thread(start = true) {
        for(i in 1..10) {
            println("Thread1: 현재 숫자는 ${i}")
            runBlocking {
                launch {
                    delay(1000)
                }
            }
        }
    }

    thread(start = true) {
        for(i in 51..60) {
            println("Thread2: 현재 숫자는 ${i}")
            runBlocking {
                launch {
                    delay(1000)
                }
            }
        }
    }
}

5. 코루틴 - 코루틴, suspend, withContext

코루틴은 일시 중단 가능한 계산의 인스턴스라고 정의하고 있다. 다른 코드와 동시에 작동하는 코드를 실행한다는 점에서 개념적으로 스레드와 유사하다고 할 수 있으나 코루틴은 특정 스레드에 바인딩되지 않는다. 한 스레드에서 실행을 일시 중단하고 다른 스레드에서 다시 시작하는 것이 가능하다.
일반적으로 launch와 async 빌더를 많이 사용한다. launch는 결과값이 없는 코루틴 빌더를 의미한다. lanuch는 다양한 함수를 가지고 있는 Job객체로 코루틴을 관리할 수 있다. join은 현재의 코루틴이 종료되기를 기다리도록 하는 함수이고, cancel은 현재의 코루틴을 즉시 종료한다.

코루틴은 스코프로 범위를 지정할 수 있다.
GlobalScope : 앱이 실행된 이후에 계속 수행해야 할 때 사용한다.
CoroutineScope : 필요할 때만 생성하고 사용 후에 정리가 필요하다.

코루틴을 실행할 쓰레드를 Dispatcher로 지정할 수 있다.
Dispatchers.Main: UI와 상호작용하기 위한 메인쓰레드
Dispatchers.IO: 네트워크나 디스크 I/O작업에 최적화되어있는 쓰레드
Dispatchers.Default: 기본적으로 CPU최적화되어있는 쓰레드
자세한 내용은 withContext 링크에서 다룬다.



그 외에 복습을 하는 과정에서 헷갈리거나 이해가 안되거나 몰랐던 부분을 찾아보면서 봤던 좋은 참고사항들을 링크로 남겨두었다.

constroctor, init

정규표현식

0개의 댓글