[내일하이] Kotlin 웹 마스터:Java에서 함수형까지, 실무형 웹 마이그레이션 완전 정복#03 when, 반복문, 예외처리

부트챌린지/내일하이:Kotlin 웹마스터
2026.07.22

 

오늘은 when과 반복문, 예외처리에 대해서 배웠다.

 

when

Kotlin에서 when은 기존 Java의 `switch-case`를 개선한 형태다. 주된 사용법은 다음과 같다.

when (x) {
    1 -> println("x는 1입니다.")
    2, 3 -> println("x는 2 또는 3입니다.") // 여러 조건 묶기
    else -> println("그 외의 값입니다.")
}

 

일단 `break;` 표현식이 사라지고, `case`를 `->`와 같은 화살표로 대체한다. 이렇게 가독성이 더 좋아진 부분도 있지만, 그 외에도 개선된 부분이 많다.

 

  • 표현식 지원
    조건에 따른 결과값을 변수에 바로 대입할 수 있다. 즉, `val a = when(b) { ... 조건 }` 와 같은 형태 표현이 가능해졌다.
  • 범위 및 컬렉션 검사
    값이 특정 범위 안에 포함되어 있는지 (`in`), 혹은 포함되지 않았는지 (`!in`) 바로 체크할 수 있다.
  • 타입 검사 및 스마트 캐스트 가능
    변수의 타입을 검사할 수 있으며, 조건문 안에서는 자동으로 해당 타입으로 형변환 된다.
  • if-else if 대체
    when 뒤에 괄호와 인자를 생략하면, 각 분기 조건문이 `Boolean` 형태로 작동해 대체할 수 있게 되었다.
  • 조건부에 표현식 사용 가능 및 분기 조건에 표현식 사용 가능
    변수만이 아니라 반환하는 모든 연산식이나 함수 호출을 넣을 수 있고, 분기 처리 조건에도 사용이 가능하다.

이러한 `when()`의 사용에서 특기할 점은 모든 경우의 수가 처리되었는지 확인한다는 점이다. 만약 모든 경우의 수가 처리되지 않을 경우, `else` 분기를 적지 않으면 컴파일 에러가 나게 된다. 실무에서는 후에 값이 추가되거나 할 때를 생각하여 모든 경우의 수를 처리하고, `else`를 제외한다. 만약 기타 처리가 필요하면 `enum`에 `OTHER`을 추가하는 등으로 처리한다. 이렇게 하면 경우의 수를 누락하는 걸 미연에 방지할 수 있다. 기존에 Java에서도 `enum`과 짝꿍으로 `switch-case`를 썼듯이, `when()`도 `enum`과 같이 사용되는 경우가 많으니 참고하면 좋다. 아래 코드는 `enum`과 `when()`의 대표적인 예제다.

 

enum class Pet {
    DOG, CAT, BIRD, OTHER
}

fun petVoice(pet: Pet) = when (pet) {
    Pet.DOG -> println("멍멍")
    Pet.CAT -> println("야옹")
    Pet.BIRD -> println("짹짹")
    Pet.G -> println("기타 동물")
}

 

 

반복문

반복문은 Java와 비슷하게 `for`과 `while`을 사용한다. `while`의 경우는 기본 Java와 비슷하지만, `for`의 경우는 많이 다르다. 먼저 Kotlin의 range 처리가 많이 달라졌다.

 

기존 Java의 경우 for i 문을 작성할 경우 `for(int i = 0; i < 10; i++;)` 로 헤더를 작성했지만, Kotlin은 range를 나타낼 때 `..`나 `until`을 사용한다. `..`의 경우 상한값을 포함하는 범위라는 의미이고, `until`은 상한값을 미포함하는 범위이다. 따라서 앞서의 for i문을 작성하려면 `for (i in 0 until 10)` 혹은 `for(i in 0..9)`로 작성하게 된다. 2단계씩으로 넘긴다고 치면, `for (i in 0 until 10 step 2)` 형태로 작성한다. 반대로 거꾸로 내려가는 키워드 또한 생겼는데, `downTo`이다. `for (i in 10 downTo)` 형태로 작성하며, 기존 `i--`와 비슷하다고 할 수 있다.

 

그리고 무엇보다 `List` 등에서의 반복 처리가 달라졌다. 

 

    val list = listOf("사과", "바나나", "포도")
    // value값을 출력한다
    for (value in list) {
        println("${value}")
    }
    
    // value값을 index와 같이 출력한다
    for ((index, value) in list.withIndex()) {
        println("${index}번째 과일은 ${value}입니다.")
    }

 

Kotlin에서는 컬렉션으로 `list`나 `map`을 주로 쓰며 선언은 `listOf`로 한다. 그 후 컬렉션 내를 순회할 때는 `for(value in list)` 형태로 순회하는데, 이 형태는 향상된 for문과 비슷하며, 실제로는 Python의 문법과 굉장히 유사하다. 그리고 `withIndex()` 키워드의 사용으로 `index` 값의 출력 또한, 기존 `i`값을 출력하던 Java와 달라졌다고 볼 수 있다.

 

다른 컬렉션으로는 `map` 또한 존재하는데, 길게는 아니지만 짧게 언급하여서 언급하고 넘어간다. `map`은 다음과 같이 사용하는 컬렉션이다.

 

val maps = mapOf(
    "사과" to "빨강",
    "바나나" to "노랑",
    "수박" to "초록"
)

for (item in maps) {
    println("${item.key}는 ${item.value}색")
}

 

Map은 `mapOf(Key to Value)` 형태로 선언하며, 자바와 달리 to라는 직관적인 키워드로 키와 값을 연결한다. `for (item in list)` 형태로 순회하며 `item.key`와 `item.value`로 접근할 수 있다. 본질적으로 `key-value` 값을 가진단 점에서 Java의 `Map`과 동일하지만, 좀 더 문법이 간단해졌다고 보면 된다.

 

예외처리

마지막으로 예외 처리를 이야기한다. Kotlin의 가장 큰 특이점은 `checked exception`을 따로 구분하지 않고 전부 unchecked로 취급한다는 점이다. 자바에서는 `IOException`이나 `SQLException` 같은 체크 예외가 발생하면 반드시 `try-catch`로 감싸거나 메서드에 `throws`를 선언해야 컴파일이 가능했다. 하지만 실무에서는 의미 없는 `try-catch`로 코드가 지저분해지거나 에러를 그냥 뭉개버리는 부작용이 많았기 때문에, Kotlin에서는 이를 과감히 제거했다.

 

val result = try {
    throw Exception("에러 발생!") // new 키워드 없음
} catch (e: Exception) {         // 변수명: 타입 형태
    "기본값"                     // catch 블록의 결과가 변수에 대입됨
}

 

Kotlin에서 예외 처리 또한 `new` 키워드를 사용하지 않으며, `try-catch` 또한 표현식으로 나타낼 수 있다. 그리고 기존 Java에서는 ` catch(Exception e)` 형태로 나타내던 헤더가, `catch(e:IOException)` 형태로 나타나는 것이 차이점이다.

 

다만 이러한 Kotlin에서 주의해야 할 점이 있는데, 바로 SpringBoot 등에서 사용하는 `@Transactional`을 사용한 트랜잭션이다. 예를 들어 A 메서드가 무사히 실행된 다음 B 메서드가 오류가 났을 경우, 개발자는 A 메서드도 롤백하고 싶지만 Kotlin은 unchecked 상태이기 때문에 그대로 롤백하지 않는다. 그렇기 때문에 두 가지 방법으로 롤백을 할 수 있도록 처리하는데, 하나가 `@Transactional(rollbackFor = [Exception::class])`이고, 다른 하나는 관심사를 분리하는 방법이다. 즉, 예외 처리가 발생할 수 있는 두 메서드를 분리하여, A 메서드와 B 메서드를 각각 별도의 트랜잭션 범위(독립된 서비스 클래스 등)로 나누어 관리하거나, 하나의 상위 '퍼사드(Facade)' 서비스에서 각각의 트랜잭션을 제어하는 방식이다.

 

예를 들어, 메인 비즈니스 로직(A)과 언제든 실패할 수 있는 외부 연동 로직(B)이 묶여 있다면, B 메서드의 상위에 `@Transactional(propagation = Propagation.REQUIRES_NEW)` 같은 옵션을 주어 독립된 트랜잭션으로 격리한다. 이렇게 하면 B가 실패하더라도 비즈니스 영향 범위가 분리되어, 개발자가 의도한 대로 전체 흐름을 안전하게 롤백하거나 예외 처리를 유연하게 격리할 수 있게 된다.

 

실제로 첫 번째 방식인 `rollbackFor`의 방식보다는 관심사 분리 방법이 더 자주 사용되고 있다. 그 이유는 하나의 트랜잭션 블록(`@Transactional`) 안에 너무 많은 책임을 밀어 넣으면, 메인 비즈니스 로직과 부가적인 로직이 엉켜 한쪽의 실패가 시스템 전체의 장애로 번질 수도 있기 때문에, 차라리 사람이 좀 더 고생하는 관심사 분리를 하게 되었다.

 


 

지금까지 들어봤을 때, 이 강의는 Kotlin만 처음 배우는 사람에게는 매우 비추천한다. 애초에 마이그레이션 강의라는 게 타이틀에도 적혀 있고, 강사님도 그렇고 전반적으로 Java를 안다는 가정 하에 설명을 하고 있기 때문에, Java를 못 다루는 사람은 Java를 먼저 알고난 뒤에 이 강의를 듣는걸 추천한다.

 

다음부터는 인터페이스와 클래스, 상속자 등 기존 Java와 비슷한 방법을 배울 예정이다.

«   2026/07   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31