[TIL] #6 Useful Features of Kotlin

Yeon·2023년 7월 22일
0
post-thumbnail

💡 to<dataType>( )

  • 자료형을 변환할 수 있음
  • toByte(), toShort(), toInt(), toLong(), toFloat(), toDouble(), toChar(), toString()
  • 사이즈가 큰 데이터 형에서 작은 데이터 형으로 변환 시에는 값이 손상될 수 있으므로 유의해야 함

💡 is & !is

is!is 키워드를 활용해서 자료형의 타입을 확인할 수 있음

<object> is <type>
<object> !is <type>
if (name is String) {
    println("name은 String 타입이다.")
} else {
    println("name은 String 타입이 아니다.")
}

💡 Pair & Triple

  • 일반적으로 함수는 1개의 결과값 또는 객체만 리턴함
  • 코틀린에서는 2개 또는 3개의 결과를 리턴함
  • 이 때 사용되는 것이 Pair와 Triple!
  • 객체를 선언할 때 내부 객체들의 타입이 달라도 상관 없음
    • 타입은 생략해도 됨

1. Pair

  • Pair 안에 저장된 객체 접근
    • first, second
    • component1(), component2()
val x = Pair<String, String>("Hello", "Kotlin")
val y = Pair<Int, String>("119", "Fire Station")

println("x.first")
println("y.second")

println("x.component2()")
println("y.component1()")


// [output]
// Hello
// Fire Station
// Kotlin
// 119

  • 함수에서 리턴되는 Pair를 변수에 바로 할당하게 만들 수도 있음
  • 함수는 Pair 객체를 리턴하고 선언한 변수에 바로 할당됨
  • 함수를 정의해줄 때 어떤 타입의 Pair 객체를 리턴할지 명시적으로 정의해줘야 함
fun makePair(first: String, second: String): Pair<String, String> {
    return Pair(first, second)
}

val (hello, kotlin) = makePair("Hello", "Kotlin")

  • toList()를 사용하면 리스트로 변환할 수 있음
val list = pair.toList()
println(list[0])
println(list[1])

2. Triple

  • Triple 안에 저장된 객체 접근
    • first, second, third
    • component1(), component2(), component3()
val z = Pair<String, String>("Hello", "Kotlin", 
"Bye"
)

println("z.first")
println("z.second")
println("z.third")

println("z.component1()")
println("z.component2()")
println("z.component3()")

  • Triple을 변수에 할당하는 방법은 Pair과 동일함
val (hello, world, bye) = Triple("Hello", "World", "Bye")

  • 리스트로 변환할 수도 있음
val list = triple.toList()

println(list[0])
println(list[1])
println(list[2])



💡 Scope Function

  • 객체 컨텍스트 내에서 코드 블록을 실행할 수 있게 하는 함수
  • 람다식을 이용해서 호출하면 Scope 안에서 Context Object(it 또는 this)를 통해서 객체에 접근 가능

0-1. 사용 이유

  • 코드가 간결해짐
  • 객체의 반복적인 사용을 줄일 수 있음
  • Null 처리가 용이
  • 객체 자체를 반환해서 체인 형식으로 계속적인 호출 가능

0-2. 특징

  • 5가지 종류: apply, let, run, with, also
  • 이들 사이에는 두 가지 주요 차이점이 존재
    • Return Value (lambda result / context object)
    • Context Object를 참조하는 방법 (it / this)
  • return value 가 lambdadls 경우는 코드 블록 내에서만 적용
  • context object는 object가 변경

1. apply

val ars = Person("ars").apply {
    age = 25
    city = "Seoul"        
}
println(ars)
  • 반환: context object
  • 코드 블럭 내 참조: this
  • 사용: 주로 객체를 초기화할 때 사용

2. run

val service = MultiportService("https://example.kotlinlang.org", 80)

val result = service.run {
    port = 8080
    query(prepareRequest() + " to port $port")
}
  • 반환: lambda result
  • 코드 블럭 내 참조: this
  • 사용: 코드 블록 내에서 값을 변경하고 확인할 때 사용

3. with

val nums = mutableListOf("one", "two", "three")
with(nums) {
    println("'with' is called with argument $this")
    println("It contains $size elements")
}
  • 반환: lambda result
  • 코드 블럭 내 참조: this
  • 사용: 해당 Object를 여러번 사용할 때 반복을 줄이기 위해 사용

4. also

val nums = mutableListOf("one", "two", "three")
nums
    .also { println("The list elements before adding new one: $it") }
    .add("four")
  • 반환: lambda result
  • 코드 블럭 내 참조: this
  • 사용: 코드 블록 내에서 값을 변경하고 확인할 때 사용

5. let

val nums = mutableListOf("one", "two", "three", "four", "five")
nums.map { it.length }.filter { it > 3 }.let { 
    println(it)
    // and more function calls if needed
} 


val str: String? = "Hello"   
val length = str?.let { 
    println("let() called on $it")        
    processNonNullString(it)      
    it.length
}
  • 반환: lambda result
  • 코드 블럭 내 참조: it
  • 사용: object 자체의 변경보다는 object의 argument를 보고 싶을 때 주로 사용

6. 정리

FuctionObject referenceReturn valueIs extension function
letitLambda resultYes
runthisLambda resultYes
run-Lambda resultNo: called without the context object.
withthisLambda resultNo: takes the context object as an argument.
applythisContext objectYes
alsoitContext objectYes



💡 Extension Functions

  • 상속이나 디자인 패턴 없이 클래스를 간단하게 확장할 수 있는 방법
  • 어떤 클래스의 멤버 메서드인 것처럼 호출할 수 있지만 그 클래스의 밖에서 선언된 함수
  • 따로 상속받지 않고 하나의 클래스에 추가적인 메서드를 구현하고 싶을 때 사용하는 함수
  • 새로운 클래스를 만드는 번거로움을 줄일 수 있음
  • 확장 함수는 각 클래스에 정적 메서드로 생성되기 때문에 virtual method invocation이 발생하지 않음
fun ReceiverType.functionName(parameters: ParameterType): Return Type {
    // 함수 구현
    // ReceiverType 클래스의 인스턴스는 'this'로 참조 가능
    // 새로운 기능 구현
    return returnValue
}
  • ReceiverType 확장 함수가 추가될 클래스의 타입
  • functionName 확장 함수의 이름
  • parameters 함수에 전달되는 매개변수들
  • ReturnType 함수가 반환하는 값의 타입

fun main() {
    fun Student.getGrade() = println("학생의 등급은 ${this.grade} 입니다")
    var student = Student("Ars", 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} 입니다")
    }
}
  • 이름 나이만 출력하는 displayInfo 메소드에 등급까지 조회하는 기능을 추가
  • 클래스를 변경하지 못하는 상황에서 확장함수로 메소드를 추가해서 사용할 수 있음



💡Synchronous vs Asynchronous

Synchronous

  • 동기이란 요청에 대한 응답이 동시에 보장되고 다음 작업을 하는 방식
  • 장점: 진행 진행할 순서에 맞게 차례대로 요청에 대한 응답을 확인하며 간단하게 작성 가능
  • 단점: 요청에 대한 응답을 얻을 때까지 대기해야하는 Block 상태가 됨

Asynchronous

  • 비동기란 요청에 대한 응답이 동시에 보장되지 않고 응답을 기다리지 않고 다음 작업을 하는 방식
  • 장점: 요청에 대한 결과를 기다리지 않으므로 다른 작업을 동시에 할 수 있음
  • 단점: 동기보다 복잡함
  • 코틀린에서 권장하는 비동기 프로그래밍 솔루션으로 Coroutine이 있음



💡 Thread

  • 프로세스 내에서 실행되는 독립적인 실행 흐름

  • 멀티스레딩을 통해 동시에 여러 작업을 처리할 수 있으며, 이를 통해 CPU 자원을 효율적으로 사용할 수 있음

  • Thread를 다룰 때 주의해야 할 점: 공유 자원에 대한 동기화

    • 여러 스레드가 공유 자원에 동시에 접근할 때, 정확하지 않은 결과가 발생하거나 의도치 않은 동작이 발생할 수 있음
    • 동기화를 통해 이러한 문제를 방지해야 함



💡 코루틴

  • 비동기 프로그래밍을 더욱 쉽고 간결하게 다룰 수 있도록 지원하는 기능
  • 최적화된 비동기 함수를 사용
  • 하드웨어 자원의 효율적인 할당 가능
  • 안정적인 동시성, 비동기 프로그래밍을 가능하게 함
  • 코루틴이 쓰레드보다 더욱 가볍게 사용할 수 있음
  • 로직들을 협동해서 실행하자는게 목표이고 구글에서 적극 권장함

1. suspend 함수

  • 코루틴은 suspend 함수 안에서 실행됨
  • 이 함수는 일시적으로 실행을 멈추고 다른 코루틴에게 실행을 양보하는 특징을 가짐

2. launch & async

  • 코루틴을 실행하기 위해 launch와 async 함수를 사용

1) launch

새로운 코루틴을 실행하고, 결과를 반환하지 않음

2) async

새로운 코루틴을 실행하고, 결과를 반환하는 Deferred 객체를 생성함

3. CoroutineScope

  • 코루틴은 CoroutineScope 내에서 실행됨
  • 일반적으로 MainScope 또는 특정 스코프를 만들어서 사용함

4. await

async 함수로 생성된 Deferred 객체는 await 함수를 사용하여 비동기 작업의 결과를 기다릴 수 있음

5. 예시

import kotlinx.coroutines.*

fun main() {
    println("메인 스레드 시작")
    
    // 코루틴 스코프 생성
    val coroutineScope = CoroutineScope(Dispatchers.Default)
    
    // launch를 사용한 코루틴 실행
    coroutineScope.launch {
        delay(1000) // 1초 대기
        println("코루틴 실행 완료")
    }
    
    println("메인 스레드 종료")
    
    // 코루틴이 완료될 때까지 대기
    Thread.sleep(2000)
}
  • launch 함수를 사용하여 새로운 코루틴을 실행함
  • delay 함수를 사용하여 1초 동안 일시 정지한 후 메시지를 출력함
  • Thread.sleep(2000)을 통해 메인 스레드가 코루틴이 끝날 때까지 기다리도록 함



[참고 사이트]

'[Kotlin] Pair, Triple', iamjm29
'[Kotlin] 코틀린 차곡차곡 - 8. Scope Function', 사바라다는 차곡차곡
'Scope functions', Kotlin
'[Kotlin] 동기? 비동기?', twaun.log
'Android의 Kotlin 코루틴', developers

0개의 댓글