Kotlin 기본 문법 정리 -2 (흐름 제어)

김민제·2022년 7월 13일
0
post-thumbnail

머리말

❓흐름제어는 kotlin뿐만 아니라 모든 언어에서 사용된다.
조건문은 알고리즘이나 코딩 문제를 해결할 때 유용하게 사용되므로 사용법을 꼭 숙지하고 있는 것이 좋다.

흐름 제어(Control Flow)

if-else(조건문)

if-else의 기본 구조는 if(조건){실행 코드 1}else{실행 코드 2}인데 조건이 참이면 실행 코드 1을 실행하고 조건이 거짓이면 실행 코드 2를 실행하는 조건문이다. 조건문은 Stateful logic(Stateful logic은 나중에 기회가 된다면 다루겠다.) 나타내는데 유용하지만 작성 시 반복될 수 있다.
조건문을 사용할 때 암시적으로 각 조건부 분기는 마지막 줄에 표현식의 결과를 반환하므로 return 키워드를 사용할 필요가 없다.

일반적 if문

if (count == 42) {
    println("I have the answer.")
} else if (count > 35) {
    println("The answer is close.")
} else {
    println("The answer eludes me.")
}

이 코드를 간략하게 하면 아래의 코드로 나타낼 수 있다.

val answerString: String = if (count == 42) {
    "I have the answer."
} else if (count > 35) {
    "The answer is close."
} else {
    "The answer eludes me."
}

println(answerString)

범위지정 if문

val answerString: String = if (count in 1..10) {
    "1~10안에 들어갑니다."
} else if (count in 11..20) {
    "11~20안에 들어갑니다."
} else {
    "1~20안에 들어가지 않습니다."
}

println(answerString)

저번 게시물에서 공부했듯이 유형 추론을 사용하여 answerString에 명시적 유형 선언을 생략할 수 있지만 명확히 하기 위해 유형 선언을 포함하는 것이 좋다.

when문

when문은 Java의 switch문을 대체한다.기존의 switch 문은 break를 사용하여 절을 구분하지만 when은 {} 중괄호를 사용하여 절을 구분한다. 조건문의 복잡도가 증가하면 if-else 표현식을 when 표현식으로 교체할 것을 고려해야한다. when 표현식의 각 분기는 조건, 화살표(->) 및 결과로 표시된다. 화살표 왼쪽 조건이 참으로 평가되면 오른쪽의 표현식 결과가 반환된다.

answerString = when {
	count == 42 -> "I have the answer."
    count > 35 -> "The answer is close."
    else -> "The answer eludes me."
    }
println(answerString)

when도 범위 지정으로 사용할 수 있고 when(변수)로 사용할 수도 있다.

val answerString: String = when(count){
	in 1..10 -> {
    	println("1~10안에 들어갑니다.")
        }
    in 11..20 -> {
    	println("11~20안에 들어갑니다.")
        }
    else -> println("1~20안에 들어가지 않습니다.")

for문

kotlin에서 for문은 다양한 방식으로 사용될 수 있다.

일반적인 for문 사용법

fun main(args:Array<String>){
	for(i: Int in 1..10){
    	println("$i")} //1 2 3 4 5 6 7 8 9 10
        
     val len : Int = 5
     for(i in 1..len){
     	println("$i")} //1 2 3 4 5 
      
     for(i in 1 until len)
     	println("$i")} // 1 2 3 4
        
     for(i in 1..10 step(2)){
     	println("$i")} // 1 3 5 7 9
     //step은 음수를 지원하지 않음
     
     for(i in 10 downTo 1){
     	println("$i")} // 10 9 8 7 6 5 4 3 2 1
        
     for(i in 10 downTo 1 step(2)){
     	println("$i")} // 10 8 6 4 2

for문으로 배열 출력하기

배열을 for문으로 출력할수 있고 Java나 C++에서의 iterator로 생각하면 된다.
또한 배열의 메소드인 .reversed()나 .count()함수를 이용하여 더 유연하게 사용할 수 있다.

fun main(args:Array<String>){
	var arr : IntArray = intArrayOf(10, 20, 30, 40, 50)
    
    for(i in arr){
    	println("$i")} // 10 20 30 40 50
        
    for(i in arr.reversed()){
    	println("$i")} // 50 40 30 20 10
        
    val list = listOf<String>("korea", "china", "japan")
    for(i in list){
    	println("$i")} // korea china japan
        
    for(i in until list.count())
    	println("$i")} // 0 1 2

foreach

fun main(){
	for(i in 1..10){
    	println("$i")} // 1 2 3 4 5 .. 10
     
    (1..10).forEach { i ->
    	println("$i")} // 1 2 3 4 5 .. 10

위의 코드와 같이 모든 작업은 for문을 사용함으로써 해결 가능하다.
그럼에도 forEach문을 사용하는 이유를 알아보자.

반복문에 대한 시간 계산

import kotlin.system.measureNanoTime

fun loop(i: Int){
    for(i in 0..i){}
}

fun main() {
    println("ForLoop Time: " + measureNanoTime {
        for (i in 0..10000) { loop(i) }
    })

    println("ForEach Time: " + measureNanoTime {
        (0..10000).forEach { i -> loop(i) }
    })
}

일반적인 반복문에서는 for문의 performance가 우세하다. forEach문은 람다 형식으로 함수를 계속 호출하는 형태이므로 performance의 저하가 일어난다.
하지만 Collection(Java의 List, Set, Map 등)의 Class들은 Collection을 상속 받고 있으며 해당 Class들에 대한 forEach를 포함한 몇몇 함수들은 inline을 제공하기 때문에 for문보다 더 좋은 performance를 낼 수 있다.

import kotlin.system.measureNanoTime

fun loop(i: Int){
    for(i in 0..i){}
}

fun main() {
    val list = (1..100000).toList()

    println("ForLoop Time: " + measureNanoTime {
        for (i in list) { loop(i) }
    })

    println("ForEach Time: " + measureNanoTime {
        list.forEach { i -> loop(i) }
    })
}

asSequence()

asSequence() 함수를 통해 list 호출 시 최적화를 진행할 수 있다.
asSequence()는 lazy collection의 한 종류인데, 체인 형식으로 collection이 호출될 경우,(list -> filter -> map) list는 값들을 저장하기 위해 temp collection을 따로 만든 뒤 값을 저장하는데 이는 오버헤드로 이어지며 performance가 저하되는 결과로 이어진다.

import kotlin.system.measureNanoTime

fun loop(i: Int){
    for(i in 0..i){}
}

fun main() {
    val list = (1..100000).toList()

    println("ForLoop Time: " + measureNanoTime {
        for (i in list) { loop(i) }
    })

    println("ForEach Time: " + measureNanoTime {
        list.forEach { i -> loop(i) }
    })

    list.asSequence()
    println("ForEach Time: " + measureNanoTime {
        list.forEach { i -> loop(i) }
    })
}

하지만 Java 8에서는 stream에 대응되는 lazy collection이 제공됨으로써 이를 해결할 수 있게 되었다.

run

Kotlin에서는 for문의 break 문과 비슷한 방식으로 forEach문을 만들 수 있다.
return@forEach문은 for문에서의 continue와 같은 역할을 한다.
return@loop는 run이라는 람다함수를 제공함으로 써 아예 forEach문 loop를 빠져나간 것이므로 for문의 break역할을 한다.

import kotlin.system.measureNanoTime

fun loop(i: Int){
    for(i in 0..i){}
}

fun main() {
    val list = (1..10000).toList()

    println("ForEach Time: " + measureNanoTime {
        list.forEach { i -> loop(i) }
    })

    println("ForEach Time: " + measureNanoTime {
        list.forEach { i -> if(i > 5000) return@forEach
            else loop(i) }
    })

    println("ForEach Time: " + measureNanoTime {
        run loop@{
            list.forEach { i -> if(i > 5000) return@loop
                else loop(i)
            }
        }
    })
}


while문

for 반복문이 범위를 정해서 하도록 강제한다면, while 반복문의 경우에는 조건식(평가식)이 참(true)인 경우에만 반복하도록 한다. while 구문의 기본적인 구조는 아래와 같다.

while(조건){
    //실행코드
}

일반적인 while문 사용법

while문을 for문에서 사용했던 것처럼 1..10을 출력하는 코드를 구성해보면 다음과 같다.

fun main(){
    var i = 1
    
    while(i <= 10){
        println(i) // 1 2 3 4 5 6 7 8 9
        i++
    }
    println(i) // 10
}

while문 무한 반복

while문은 무한 반복을 사용할 수 있다. 무한 반복을 사용하는 이유는 여러 가지가 존재하지만 주로 어떤 대상을 반복적으로 검사할 때 사용한다. 대표적으로 컴퓨터 프로그램의 백신 검사, 채팅 프로그램의 채팅 메시지 수신, 디지털시계 등이 있다. 이런 작업은 모두 사용자가 보는 앞이 아닌 뒤에서 작업이 이루어지는데 뒤에서 작업하는 프로그램을 백그라운드 프로그램, 실제 프로그램이 보인다면 포그라운드 프로그램이라고 부른다. 무한 반복을 사용하는 방법은 조건식에 true를 넣어주면 된다.

while(true){
    //commands
}

do-while문 사용법

while 반복문의 경우에는 처음부터 조건을 판단해서 해당 조건이 맞지 않다면 내부 명령을 실행하지 않지만, do-while의 경우에는 최소 한 번은 실행해야할 때 사용한다. do-while문의 구조는 다음과 같다.

do{
    실행 코드
}while(조건)

먼저 do{}안에 있는 실행 코드를 한번 실행시키고 while문의 조건을 판별해 참이라면 실행코드를 반복한다.
또는 사용자로부터 어떤 입력을 받을 때, 입력으로 특정한 문자열이 들어올 시 종료해야하는 경우에도 do-while 구문을 유용하게 사용할 수 있다.

fun main(args:Array<String>){
    var str:String?
    do{
        print("input: ")
        str = readLine()!!
        println("view : $str")
    }while(str != "exit")
    println("exit")
}

위 코드처럼 input으로 exit를 받으면 프로그램이 종료되는 코드도 구성할 수 있다.

정리

  • 흐름 제어문은 대부분의 언어에서 유용하게 사용되므로 필요에 따라 if-else, for, when 등의 사용법을 잘 익혀놓는 것이 좋다. 특히 when문은 kotlin에서 코드를 간략하게 만들어주는 경우가 많기 때문에 자주 사용된다.
profile
우매함의 봉우리를 넘어 깨달음의 오르막으로..!🏃🏃

0개의 댓글