KotlinLanguage

Kotlin 무작정 따라해보기 5일차 - Control Flow

5일차 시작하기 - Control Flow

지난 4일차까지 Introduction의 내용으로 코틀린의 기본적인 Functions(함수), Classes(클래스), Variables(변수) 와 Generics(제네릭), Null Safety, Inheritance(상속)에 대하여 학습하였다.
오늘은 모든 언어의 가장 기본적인고 빠질 수 없는 Control Flow(제어흐름, 제어문, 제어식)에 대해 학습을 시작한다. 코틀린도 다른 언어와 별반 다르지 않을 것이라 생각된다. 학습을 하면서 무엇이 다른지 확인해보자.

When

처음부터 신기한 녀석이 나왔다. 일반적인 언어에서 사용하는 switch와 같은 녀석이다. 코틀린에서는 더 유연하고 명확한 구성을 위해 when이라는 이름을 붙인 것 같다.
문(statement) 또는 식(expression)으로 사용 가능하다.

When Statement

fun main() {
    cases("Hello")
    cases(1)
    cases(0L)
    cases(MyClass())
    cases("hello")
}

fun cases(obj: Any) {                                
    when (obj) {                                     // 1   
        1 -> println("One")                          // 2
        "Hello" -> println("Greeting")               // 3
        is Long -> println("Long")                   // 4
        !is String -> println("Not a string")        // 5
        else -> println("Unknown")                   // 6
    }   
}

class MyClass

/* 실행 결과
Greeting
One
Long
Not a string
Unknown
*/
  1. whenstatement로 사용하여 정의한다.
  2. obj가 숫자(Number) 1일 경우 One이라는 값을 출력한다.
  3. obj가 문자(String) Hello일 경우 Greeting이라는 값을 출력한다.
  4. objLong타입일 경우 Long이라는 값을 출력한다.
  5. objString타입이 아닌 경우 Not a string이라는 값을 출력한다.
  6. obj가 위 모든 조건이 아닌 경우 Unknown이라는 값을 출력한다.

    • 일반적인 switch문의 default와 같다.
    • 일발적인 언어의 switch 구조와 비교하면 case라는 키워드를 사용하지 않고, 기본값을 표현하는 default대신 else를 사용한다.

       // Javascript의 경우
       switch(obj) {
          case [일치하는 값]:
             // 일치할 경우 실행할 내용
             break;
          default:
             // 일치하는 것이 없을 경우 실행할 내용
       }
      
       // 코틀린의 경우
       when(obj) {
          [조건] -> [리턴할 값 or 실행할 내용]
          [조건] -> {
             // 여러줄의 실행할 내용
          }
          else -> [리턴할 값 or 실행할 내용] // 위 조건에 일치하는 내용이 없을 경우 실행할 내용
       }
       // 한 줄로 표현할 때는 `{}`를 생략하고, 여러 줄을 표현할 때는 `{}`로 감싸준다.
      
    • 다른 언어의 switch는 일치하는 값이면, 해당 case의 시작에서 break가 나올 때까지의 내용을 수행했지만, 코틀린의 when{}로 감싸주거나 한 줄로 표현한다. break라는 것이 없고, 분기의 조건에 일치하면 해당 분기를 바로 실행한다.

When Expression

fun main() {
    println(whenAssign("Hello"))
    println(whenAssign(3.4))
    println(whenAssign(1))
    println(whenAssign(MyClass()))
}

fun whenAssign(obj: Any): Any {
    val result = when (obj) {                   // 1
        1 -> "one"                              // 2
        "Hello" -> 1                            // 3
        is Long -> false                        // 4
        else -> 42                              // 5
    }
    return result
}

class MyClass

/* 실행 결과
1
42
one
42
*/
  1. whenexpression으로 사용하여 정의한다.

    • obj의 값에 따라 result에 담기는 값이 달라지게 된다.
  2. obj가 숫자 1인 경우 resultone을 설정한다.
  3. objHello인 경우 result에 숫자 1을 설정한다.
  4. objLong타입인 경우 result에 Boolean false을 설정한다.
  5. obj가 위 조건에 모두 맞지 않을 경우 result42를 설정한다.

이렇게 whenexpression으로 사용하는 경우에는 모든 조건이 일치하지 않는 경우를 대비하여 마지막 else를 넣어주는 것이 좋다.
추가로, switch의 경우 값과 일치하는 case가 나오고 break가 없으면 break가 나올 때까지 하위 case의 내용도 모두 수행하였다. 이 특성을 이용해서 case가 다르더라도 동일한 결과를 주도록 코드를 작성할 수 있었다. 코틀린에서는 조건이 일치하는 경우 해당 분기의 내용만 실행하기 때문에 이전의 switch할 수 없다. 대신, when의 경우 각 분기의 조건을 입력하기 때문에 조건으로 처리하거나, 값을 콤마(,)로 구분하여 나열하면 된다.

위 이미지에서처럼 in을 이용하여 범위를 지정하여 해당 범위의 값이 일치하는 경우 또는 !in을 이용하여 해당 범위가 아닌 경우를 조건으로 사용할 수 있고, is!is를 이용하여 타입을 검사할 수 있다.
when의 특이점으로 인자(argument)를 전달하지 않으면, 분기 조건이 단순 Boolean 표현이 되어 if-else if와 같은 표현을 대체해서 사용할 수 있다.

   when {
      x.isOdd() -> print("x is odd")
      y.isEven() -> print("y is even")
      else -> print("x+y is odd.")
   }

코틀린 1.3버전부터는 아래와 같은 표현도 가능하다.

  fun Request.getBody() =
        when (val response = executeRequest()) {
              is Success -> response.body
              is HttpError -> throw HttpException(response.status)
        }

https://kotlinlang.org/docs/reference/control-flow.html#when-expression 참고

Loops

코틀린은 다른 언어에서 일반적으로 사용되는 for, while, do-while을 지원한다.
loop안에서 사용 가능한 breakcontinue도 지원한다.
참고: https://kotlinlang.org/docs/reference/returns.html

for

코틀린에서 for의 동작은 방식은 대부분의 다른언어와 동일하다. 코틀린 레퍼런스에서는 C#과 같은 언어의 foreach와 비슷하다고 한다.
그리고 iterator를 제공하는 모든 것을 반복한다. 참고: https://kotlinlang.org/docs/reference/control-flow.html#for-loops

val cakes = listOf("carrot", "cheese", "chocolate")

for (cake in cakes) {                               // 1
    println("Yummy, it's a $cake cake!")
}
  1. cakes 목록의 각 항목을 반복한다.

while and do-while

코틀린에서의 while, do-while의 동작 방식도 대부분의 다른언어와 동일하다. 코틀린의 레퍼런스에서도 다른 내용은 없었다.
참고: https://kotlinlang.org/docs/reference/control-flow.html#for-loops

fun eatACake() = println("Eat a Cake")
fun bakeACake() = println("Bake a Cake")

fun main(args: Array<String>) {
    var cakesEaten = 0
    var cakesBaked = 0
    
    while (cakesEaten < 5) {                    // 1
        eatACake()
        cakesEaten ++
    }
    
    do {                                        // 2
        bakeACake()
        cakesBaked++
    } while (cakesBaked < cakesEaten)

}
  1. cakesEaten < 5 조건이 성립하는 동안 블럭안의 내용을 반복해서 수행한다.
  2. cakesBaked < cakesEaten 조건의 성립과 관계 없이 1회 반복 후 조건이 성립하면 반복 수행한다.

Iterators

iterator 연산자를 클래스에 추가하여 클래스에 iterator를 정의한다.

class Animal(val name: String)

class Zoo(val animals: List<Animal>) {

    operator fun iterator(): Iterator<Animal> {             // 1
        return animals.iterator()                           // 2
    }
}

fun main() {

    val zoo = Zoo(listOf(Animal("zebra"), Animal("lion")))

    for (animal in zoo) {                                   // 3
        println("Watch out, it's a ${animal.name}")
    }

}
  1. Zoo 클래스에 operator 키워드를 이용하여 iterator를 정의한다. 이름은 반드시 iterator야 한다.

    operator 키워드는 2일차에 학습했던 Operator Functions를 참고하자.

  2. 다음 메서드 요구사항을 충복하는 iterator를 반환합니다.

    • next(): Animal
    • hasNext(): Boolean

    1.번에서 iterator()를 정의할때 리턴타입으로 사용된 Iterator<Animal>interface Iterator는 기본으로 hasNext(), next()를 가지고 있습니다. 자세한 내용은 https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/-iterator/ 참고

  3. 1.번에서 정의한 iterator()를 이용하여, Zoo클래스를 반복하여 animal을 반복 출력합니다.

iterator는 타입이나 확장 함수(extension function)로 정의 가능하다 하는데, 좀 더 예제를 따라 하거나 레퍼런스를 참고해봐야 이해가 될 것 같다.
참고: https://kotlinlang.org/docs/reference/iterators.html

Ranges

범위를 정의하기 위해 코틀린에서 제공하는 도구(tools)이다. 다른 언어에서도 이런 방식으로 사용했던 것 같은데, 기억이 안 난다.

for(i in 0..3) {             // 1
    print(i)
}
print(" ")

for(i in 0 until 3) {        // 2
    print(i)
}
print(" ")

for(i in 2..8 step 2) {      // 3
    print(i)
}
print(" ")

for (i in 3 downTo 0) {      // 4
    print(i)
}
print(" ")
  1. 0에서 3까지 반복하는 for를 정의합니다.

    • i in 0..3i의 값이 0부터 3까지 증가하며 3을 포함한다.
    • C/C++/Java의 경우 for(i=0; i<=3; ++i)와 같이 사용한 것이다.
  2. 0에서 3이전까지 반복하는 for를 정의합니다.

    • i in 0 until 3i의 값이 0부터 2까지 증가하며 3을 포함하지 않는다.
    • Pythonfor loop와 같고, C/C++/Java의 경우 for(i=0; i<3; ++i)와 같이 사용한 것이다.
  3. 2에서 8까지 반복하는 for를 정의합니다.

    • i in 2..8 step 2i의 값이 2부터 2씩 증가하여 8을 포함한다. 출력결과는 2468이다.
    • C/C++/Java의 경우 for(i=2; i<=8; i=i+2)와 같이 사용한 것이다.
  4. 3에서 0까지 반복하는 for를 정의합니다.

    • i in 3 downTo 0i의 값이 3부터 0까지 감소하며 0을 포함한다.
    • C/C++/Java의 경우 for(i=3; i>=0; --i)와 같이 사용한 것이다.

범위로 문자(Char)도 지원한다.

for (c in 'a'..'d') {        // 1
    print(c)
}
print(" ")

for (c in 'z' downTo 's' step 2) { // 2
    print(c)
}
print(" ")
  1. a부터 d까지 알파벳 순서대로 반복된다.

    • 출력결과는 abcd이다.
  2. z부터 s까지 알파벳의 역순으로 반복된다.

    • 문자(Char)에서도 downTo, step사용이 가능하다.
    • step 2가 있어 2단계식 낮아져, 출력결과는 zxvt이다.

if에서도 이용 가능하다.

val x = 2
if (x in 1..5) {            // 1
    print("x is in range from 1 to 5")
}
println()

if (x !in 6..10) {          // 2
    print("x is not in range from 6 to 10")
}
  1. x가 범위안에 있으면 x is in range from 1 to 5를 출력한다.
  2. x가 범위안에 없으면 x is not in range from 6 to 10를 출력한다.

    • !를 사용하여 부정으로 표현한다.

Range에 대한 더 자세한 내용은 https://kotlinlang.org/docs/reference/ranges.html 참고

Equality Checks

코틀린에서의 =====의 차이점이다.
==는 구조 비교(structural comparison)를 위해 사용한다.
===는 레퍼런스 비교(referential comparison)를 위해 사용한다.

예제에서는 a == bif (a == null) b == null else a.equals(b)로 컴파일 된다고 하는데, 이게 더 무슨 말인지 모르겠다. 결국 a.equals(b)라는 것인가? 앞에 if (a == null) b == null의미는 무엇일까?

val authors = setOf("Shakespeare", "Hemingway", "Twain")
val writers = setOf("Twain", "Shakespeare", "Hemingway")

println(authors == writers)   // 1
println(authors === writers)  // 2
  1. 결과는 true이다. 이유는 authors.equals(writers)를 호출하게 되고, 안에 단계전 요소(element)의 순서를 비교하지 않는다. (순서는 사실상 무시된다.)
  2. 결과는 false이다. 이유는 authorswriters가 따로 정의하였기 때문에 별도의 레퍼런스를 가지고 있어서 이다.

    • 자칫 authorswriterssetOf한 데이터의 순서가 달라서 false일꺼라 생각할 수 있는데, 이는 전혀 상관이 없다.
    • 레퍼런스의 비교이기 때문에 애초 setOf로 정의할때 서로다른 레퍼런스를 가지게 되었으므로 ===로 비교해서는 둘은 절대 true가 나올 수 없다.

Conditional Expression

코틀린(Kotlin)은 삼항연산자(ternary operator)가 없다. 코틀린의 if포현식에서 값을 반환하기 때문이다. 그래서 if를 사용하면 된다.

fun max(a: Int, b: Int) = if (a > b) a else b         // 1

println(max(99, -42))
  1. (a > b)조건이 true일 경우 a를 리턴하고, false일 경우 b를 리턴한다.

우리가 흔히 생각하는 if와는 조금 다른듯 하다. if{}을 사용할 수 있는데, 이경우 {}의 가장 마지막줄의 내용이 리턴된다. when과 마찬가지로 조건에 따라 바로 어디가에 값을 할당하거나 리턴해야 하는 경우에는 반드시 else를 추가 해야 한다. if의 표현에 대한 좀더 자세한 내용은 https://kotlinlang.org/docs/reference/control-flow.html#if-expression 참고한다.

5일차 마무리 & Next

오늘은 코틀린(Kotlin)의 Control Flow에 대해서 학습하였다.
적어도 1주일에 하나씩은 해야지 하였는데, 생각대로 되지 않고 있다. 시작은 작년 11월 말이었는데, 벌써 새해가 밝아 1월 중순이 지나고 있다. 하지만 나의 진도는 아직도 기초에서 벗어나지 못하고 있다.
처음 계획이 예제를 하나씩 따라 하다 보면 금방 하겠지였는데, 예제 하나만으론 부족하여 자연스럽게 레퍼런스를 참고하고, 서치를 조금씩 하다 보니 내용이 늘어나고 시간이 늘어나는 것 같다. 그래도 이렇게 하나씩 하나씩 하다 보면 언젠간 다 보는 날이 올 것이라 생각하고, 학습 로그를 꾸준히 남기겠다.

다음시간에는 코틀린의 Special Classes에 대해 학습하도록 하겠다.