Kotlin/StoreInfo

<강의> Kotlin 문법 종합반 5주차

re트 2023. 11. 30. 15:04
728x90

1. 자료형 변환

  - 일반 자료형 간의 변환은 to자료형() 메소드 사용

  - 예시

fun main() {
    var num1 = 20
    var num2 = 30.2

    var num3 = num2.toInt()
    var num4 = num1.toDouble()

    var strNum5 = "10"
    var strNum6 = "10.21"

    var num5 = Integer.parseInt(strNum5) // strNum5.toInt() 도 동일한 기능
    var num6 = strNum6.toDouble()

    println("num3: $num3")
    println("num4: $num4")
    println("num5: $num5")
    println("num6: $num6")
}

  <출력결과>

num3: 30
num4: 20.0
num5: 10
num6: 10.21

 

  - 객체 자료형 간의 변환은 상속관계에서 as 키워드 사용으로 가능

  - 업 캐스팅 : 자식 클래스를 부모 클래스의 자료형으로 변환하여 객체 생성

  - 다운 캐스팅 : 부모 클래스를 자식 클래스의 자료형으로 변환하여 객체 생성

 

2. 자료형 타입 확인

  - is 키워드 사용

  - 예시

fun main() {
    val name = "23"

    if(name is String) {
        println("name은 String 타입입니다")
    } else {
        println("name은 String 타입이 아닙니다")
    }
}

  <출력결과>

name은 String 타입입니다

 

3. 복수 데이터 리턴

  - 기본적으로 메소드는 하나의 데이터를 리턴하지만 두 개 이상의 데이터를 포함하는 데이터클래스를 설계하고 인스턴스를 리턴하면 복수 데이터 리턴 가능

  - Pair를 활용하면 두 개의 인스턴스 리턴 가능, Triple을 활용하면 세 개의 인스턴스 리턴 가능

  - Pair 예시

fun main() {
    var chicken = Chicken()
    var eggs = chicken.getEggs()
    var listEggs = eggs.toList()

    // 리스트로 관리
    var firstEgg = listEggs[0]
    var secondEgg = listEggs[1]

    println("달걀의 종류는 ${eggs} 입니다.")
    println("리스트 달걀의 종류는 ${listEggs} 입니다.")
    println("첫번째 달걀의 종류는 ${firstEgg} 입니다.")
    println("두번째 달걀의 종류는 ${secondEgg} 입니다.")
}

class Chicken {
    // Pair 요소 접근은 .first, .second로 가능
    fun getEggs(): Pair<String, String> {
        var eggs = Pair("달걀", "맥반석")
        return eggs
    }
}

  <출력결과>

달걀의 종류는 (달걀, 맥반석) 입니다.
리스트 달걀의 종류는 [달걀, 맥반석] 입니다.
첫번째 달걀의 종류는 달걀 입니다.
두번째 달걀의 종류는 맥반석 입니다.

 

  - Triple 예시

fun main() {
    var chicken = Chicken()
    var eggs = chicken.getThreeEggs()
    var listEggs = eggs.toList()

    // 리스트로 관리
    var firstEgg = listEggs[0]
    var secondEgg = listEggs[1]
    var eggTime = listEggs[2]

    println("달걀의 정보는 ${eggs} 입니다.")
    println("리스트 달걀의 정보는 ${listEggs} 입니다.")
    println("첫번째 달걀의 종류는 ${firstEgg} 입니다.")
    println("두번째 달걀의 종류는 ${secondEgg} 입니다.")
    println("달걀은 ${eggTime}에 나왔습니다.")
}

class Chicken {
    fun getTwoEggs(): Pair<String, String> {
        var eggs = Pair("달걀", "맥반석")
        return eggs
    }
    // Triple 요소 접근은 .first, .second, .third로 가능
    fun getThreeEggs(): Triple<String, String, Int> {
        var eggs = Triple("달걀", "맥반석", 20230101)
        return eggs
    }
}

  <출력결과>

달걀의 정보는 (달걀, 맥반석, 20230101) 입니다.
리스트 달걀의 정보는 [달걀, 맥반석, 20230101] 입니다.
첫번째 달걀의 종류는 달걀 입니다.
두번째 달걀의 종류는 맥반석 입니다.
달걀은 20230101에 나왔습니다.

 

4. 스코프 함수

  - 객체 사용시 임시로 Scope를 만들어서 편리한 코드 작성을 도와줌

  - 종류

    1) let : 중괄호 블록 안에 it으로 자신의 객체를 전달하고 수행결과를 반환

    2) with : 중괄호 블록 안에 this로 자신의 객체를 전달하고 코드 수행

    3) also : 중괄호 블록 안에 it으로 자신의 객체를 전달하고 객체 반환

    4) apply : 중괄호 블록 안에 this로 자신의 객체를 전달하고 객체 반환

    5) run : 객체에서 호출하지 않는 경우 중괄호 블록 내의 결과 반환, 객체에서 호출하는 경우 null체크가 가능하게 코드 수행

  - let 예시

fun main() {
    var strNum = "10"

    var result = strNum?.let {
        // 중괄호 안에서는 it으로 활용함
        Integer.parseInt(it)
    }

    println(result!!+1)
}

  <출력결과>

11

 

  - with 예시

fun main() {
    var alphabets = "abcd"

    with(alphabets) {
        //var result = this.subSequence(0,2)
        var result = subSequence(0,2)
        println(result)
    }
}

  <출력결과>

ab

 

  - also 예시

fun main() {
    var student = Student("참새", 10)

    var result = student?.also {
        it.age = 50
    }
    result?.displayInfo()
    student.displayInfo()
}

class Student(name: String, age: Int) {
    var name: String
    var age: Int

    init {
        this.name = name
        this.age = age
    }

    fun displayInfo() {
        println("이름은 ${name} 입니다")
        println("나이는 ${age} 입니다")
    }
}

  <출력결과>

이름은 참새 입니다
나이는 50 입니다
이름은 참새 입니다
나이는 50 입니다

 

  - apply 예시

fun main() {
    var student = Student("참새", 10)

    var result = student?.apply {
        student.age = 50
    }
    result?.displayInfo()
    student.displayInfo()
}

class Student(name: String, age: Int) {
    var name: String
    var age: Int
    
    init {
        this.name = name
        this.age = age
    }
    
    fun displayInfo() {
        println("이름은 ${name} 입니다")
        println("나이는 ${age} 입니다")
    }
}

  <출력결과>

이름은 참새 입니다
나이는 50 입니다
이름은 참새 입니다
나이는 50 입니다

 

  - run 예시

fun main() {
    // 객체에서 호출하지 않은 경우
    var totalPrice = run {
        var computer = 10000
        var mouse = 5000

        computer+mouse
    }
    println("총 가격은 ${totalPrice}입니다")

    var student = Student("참새", 10)
    // 객체에서 호출한 경우
    student?.run {
        displayInfo()
    }
}

class Student(name: String, age: Int) {
    var name: String
    var age: Int

    init {
        this.name = name
        this.age = age
    }

    fun displayInfo() {
        println("이름은 ${name} 입니다")
        println("나이는 ${age} 입니다")
    }
}

  <출력결과>

총 가격은 15000입니다
이름은 참새 입니다
나이는 10 입니다

 

- 정리

  this (수신객체 자체를 람다의 수신객체로 전달) it (수신객체를 람다의 파라미터로 전달)
블록 수행 결과 반환 run, with let
객체 자신 반환 apply also

 

5. 확장함수

  - 클래스 외부에서 클래스의 메소드 추가하는 것

  - 원하는 메소드가 있지만 내가 설계한 클래스가 아닐 때 외부에서 메소드를 관리 -> 원본 클래스의 일관성 유지 가능  

  - 주의사항

    1) public 멤버에만 접근 가능

    2) 상속 불가

  - 예시

fun main() {
    // 확장함수를 사용한 부분
    fun Student.getGrade() = println("학생의 등급은 ${this.grade} 입니다")
    var student = Student("참새", 10, "A+")
    student.displayInfo()
    student.getGrade()
}

class Student(name: String, age: Int, grade: String) {
    var name: String
    var age: Int
    var grade: String

    init {
        this.name = name
        this.age = age
        this.grade = grade
    }

    fun displayInfo() {
        println("이름은 ${name} 입니다")
        println("나이는 ${age} 입니다")
    }
}

  <출력결과>

이름은 참새 입니다
나이는 10 입니다
학생의 등급은 A+ 입니다

 

6. 비동기 프로그래밍

  - 순서대로 하나의 작업씩 수행하는 방식은 동기 프로그래밍, 여러가지 로직들이 완료 여부에 관계없이 실행되는 방식은 비동기 프로그래밍

  - 요청을 보내고 결과값을 받을 때까지 작업을 멈추는 건 동기 프로그래밍, 요청을 보내고 결과값을 받을 때까지 멈추지 않고 또 다른 일을 하는 건 비동기 프로그래밍

 

7. 쓰레드

  - 동시성 프로그래밍을 위한 기술

  - 로직을 동시에 실행할 수 있도록 도와줌

  - 프로그램에는 하나의 메인 쓰레드(fun main())가 존재

  - 동시에 로직을 실행하기 위해서는 thread 키워드로 쓰레드를 생성

  - 작업 단위 : Thread(각 Thread가 독립적인 Stack 메모리 영역을 확보)

  - 운영체제 커널에 의한 문맥 교환을 통해 동시성 보장(+ 블로킹, 스케쥴링)

  - 예시

// 사전 준비
// build.gradle.kts (Module :app)
android {
...
    // 추가
    packagingOptions {
        resources.excludes += "DebugProbesKt.bin"
    }
}

dependencies {
...
    // 추가
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3")
}
fun main() {
    thread(start = true) {
        for(i in 1..10) {
            println("Thread1: 현재 숫자는 ${i}")
            runBlocking {
                launch {
                    // 딜레이 1초
                    delay(1000)
                }
            }
        }
    }

    thread(start = true) {
        for(i in 50..60) {
            println("Thread2: 현재 숫자는 ${i}")
            runBlocking {
                launch {
                    // 딜레이 1초
                    delay(1000)
                }
            }
        }
    }
}

  <출력결과>

// 매번 다른 결과 출력
Thread1: 현재 숫자는 1
Thread2: 현재 숫자는 50
Thread2: 현재 숫자는 51
Thread1: 현재 숫자는 2
Thread1: 현재 숫자는 3
Thread2: 현재 숫자는 52
Thread1: 현재 숫자는 4
Thread2: 현재 숫자는 53
Thread2: 현재 숫자는 54
Thread1: 현재 숫자는 5
Thread1: 현재 숫자는 6
Thread2: 현재 숫자는 55
Thread2: 현재 숫자는 56
Thread1: 현재 숫자는 7
Thread2: 현재 숫자는 57
Thread1: 현재 숫자는 8
Thread2: 현재 숫자는 58
Thread1: 현재 숫자는 9
Thread2: 현재 숫자는 59
Thread1: 현재 숫자는 10
Thread2: 현재 숫자는 60

 

8. 코루틴(Light-Weight Thread)

  - 동시성 프로그래밍을 위한 기술

  - Thread를 대체하는 기술이 아닌 하나의 Thread를 작게 쪼개서 사용하는 기술

  - 사용하는 이유

    1) 최적화된 비동기 함수 사용

    2) 하드웨어 자원의 효율적 할당 가능

    3) 안정적인 동시성, 비동기 프로그래밍 가능

    4) 쓰레드보다 더 가볍게 사용 가능

    5) 구글에서 적극 권장

  - 코루틴 빌더의 종류

    1) launch : 결과값이 없는 코루틴 빌더, Job 객체로 코루틴 관리

      - Job 객체는 다양한 함수를 가지고 있음(현재의 코루틴이 종료되길 기다리는 join, 현재의 코루틴을 즉시 종료하는 cancel)

    2) async : 결과값이 있는 코루틴 빌더

   - 코루틴 범위

    1) GlobalScope : 앱이 실행된 이후에 계속 수행되어야할 때 사용

    2) CoroutineScope : 필요할 때만 생성하여 사용하고 사용 후에 정리 필요

   - 코루틴을 실행할 쓰레드는 Dispather로 지정

    1) Dispatchers.Main : UI와 상호작용하기 위한 메인쓰레드

    2) Dispatchers.IO : 네트워크나 디스크 I/O 작업에 최적화되어있는 쓰레드

    3) Dispatchers.Default : 기본적으로 CPU 최적화되어있는 쓰레드

  - 작업 단위 : Coroutine Object(작업마다 Object 할당, 객체이기 때문에 JVM Heap 영역에 적재)

  - 동시성 보장을 위해 OS는 관여하지 않고 개발자의 소스 코드를 통해 교환 시점을 마음대로 정하며(Programmer Switching or No-Context Switching) 동시성 보장

  - 예시1

fun main(args: Array<String>) {
    println("메인쓰레드 시작")
    var job = GlobalScope.launch {
        // 3초 딜레이
        delay(3000)
        println("여기는 코루틴...")
    }
    // 코루틴이 끝나기를 기다림
    runBlocking {
        job.join()
    }
    println("메인쓰레드 종료")
}

  <출력결과>

메인쓰레드 시작
여기는 코루틴...
메인쓰레드 종료

 

  - 예시2

fun main(args: Array<String>) {
    println("메인쓰레드 시작")
    // Dispatchers.Default 쓰레드 사용
    var job = CoroutineScope(Dispatchers.Default).launch {
        // 3초 딜레이
        delay(3000)
        println("여기는 코루틴...")
    }
    // 코루틴이 끝나기를 기다림
    runBlocking {
        job.join()
    }
    println("메인쓰레드 종료")
    job.cancel()
}

  <출력결과>

메인쓰레드 시작
여기는 코루틴...
메인쓰레드 종료

 

  - 예시3

fun main(args: Array<String>) {
    println("메인쓰레드 시작")
    // Dispatchers.Default 쓰레드 사용
    var job = CoroutineScope(Dispatchers.Default).launch {
        // 여러 코루틴 사용
        var fileDownloadCoroutine = async(Dispatchers.IO) {
            // 10초 딜레이
            delay(10000)
            "파일 다운로드 완료"
        }
        var databaseConnectCoroutine = async(Dispatchers.IO) {
            // 5초 딜레이
            delay(5000)
            "데이터베이스 연결 완료"
        }
        // 코루틴 결과값 받아오기 가능
        // await는 일시중단이 가능한 코루틴에서 실행가능
        println("${fileDownloadCoroutine.await()}")
        println("${databaseConnectCoroutine.await()}")
    }
    // 코루틴이 끝나기를 기다림
    runBlocking {
        job.join()
    }
    println("메인쓰레드 종료")
    job.cancel()
}

  <출력결과>

메인쓰레드 시작
파일 다운로드 완료
데이터베이스 연결 완료
메인쓰레드 종료

 

반응형