오늘은 5주차 문법을 복습했다. 헷갈리는 것들이 너무나 많아서 구글 검색을 해가며 하다보니 시간이 금방 지나가버렸다. 배운 내용을 간단히 정리하겠다.
일반 자료형은 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) {
}
자기 자신의 객체를 전달하는 함수들이라고 생각하면 편하다. 명시적으로 수신객체 자체를 람다로 전달하는 방법과 수신객체를 람다의 파라미터로 전달하는 방법으로 나뉜다.
// 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에서 접근방식 this | Scope에서 접근방식 it | |
|---|---|---|
| 블록 수행 결과를 반환 | run(null처리 가능), with | let |
| 객체 자신을 반환 | apply | also |
it과 this에 관한 것은 링크를 첨부한다. >> it, this
간단히 설명하자면 클래스 내부에서 만들지 않아도 실행할 수 있는 함수다. 원하는 메소드가 있지만 내가 설계한 클래스가 아닐때 외부에서 메소드를 관리한다. 그렇기 때문에 원본 클래스의 일관성을 유지할 수 있다는 장점이 있다.
단, public에만 접근할 수 있고 클래스의 멤버함수처럼 상속하는 건 불가능하다. 아직까지 어디에 활용해야할지 잘은 모르겠지만 언젠가 분명히 필요로 할 날이 올 것이다.
프로그램에는 하나의 메인 쓰레드(실행흐름)라는 것이 존재한다. 하나의 메인 쓰레드는 메인함수(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)
}
}
}
}
}
코루틴은 일시 중단 가능한 계산의 인스턴스라고 정의하고 있다. 다른 코드와 동시에 작동하는 코드를 실행한다는 점에서 개념적으로 스레드와 유사하다고 할 수 있으나 코루틴은 특정 스레드에 바인딩되지 않는다. 한 스레드에서 실행을 일시 중단하고 다른 스레드에서 다시 시작하는 것이 가능하다.
일반적으로 launch와 async 빌더를 많이 사용한다. launch는 결과값이 없는 코루틴 빌더를 의미한다. lanuch는 다양한 함수를 가지고 있는 Job객체로 코루틴을 관리할 수 있다. join은 현재의 코루틴이 종료되기를 기다리도록 하는 함수이고, cancel은 현재의 코루틴을 즉시 종료한다.
코루틴은 스코프로 범위를 지정할 수 있다.
GlobalScope : 앱이 실행된 이후에 계속 수행해야 할 때 사용한다.
CoroutineScope : 필요할 때만 생성하고 사용 후에 정리가 필요하다.
코루틴을 실행할 쓰레드를 Dispatcher로 지정할 수 있다.
Dispatchers.Main: UI와 상호작용하기 위한 메인쓰레드
Dispatchers.IO: 네트워크나 디스크 I/O작업에 최적화되어있는 쓰레드
Dispatchers.Default: 기본적으로 CPU최적화되어있는 쓰레드
자세한 내용은 withContext 링크에서 다룬다.
그 외에 복습을 하는 과정에서 헷갈리거나 이해가 안되거나 몰랐던 부분을 찾아보면서 봤던 좋은 참고사항들을 링크로 남겨두었다.