Developing Myself Everyday

조건 표현식


우리가 코드를 작성할 때 특정 조건에 해당하는 행위만 처리하고 싶을 때가 있습니다. 이를 위해서는 참과 거짓으로 결과를 처리하는 비교연산자논리연산자를 알아야 합니다.

 

비교연산자

연산자 의미 표현식 내부적 실행
> 크다 a > b a.compareTo(b) > 0
< 작다 a < b a.compareTo(b) < 0
>= 크거나 같다 a >= b a.compareTo(b) >= 0
<= 작거나 같다 a <= b a.compareTo(b) <= 0
== 같다 a == b a.equals(b)
!= 같지 않다 a != b !(a.equals(b))

a.compareTo(b)는 a와 b를 비교한 결과를 반환하는 함수입니다. 이 함수는 다음과 같은 반환값을 가집니다.
a < b : 음수 값(주로 -1)을 반환합니다.
a = b : 0을 반환합니다.
a > b : 양수 값(주로 1)을 반환합니다.

 

비교연산의 결과는 Boolean으로 반환됩니다.

fun main() {
    val num1 = 20
    val num2 = 30

    println(num1 == num2) // 출력: false (20과 30은 같지 않음)
    println(num1 != num2) // 출력: true (20과 30은 다름)
    println(num1 > num2)  // 출력: false (20은 30보다 작음)
    println(num1 >= num2) // 출력: false (20은 30보다 작거나 같지 않음)
    println(num1 < num2)  // 출력: true (20은 30보다 작음)
    println(num1 <= num2) // 출력: true (20은 30보다 작거나 같음)
}

 

 

특정 범위에 속한 값에 포함 여부

특정 범위에 속한 값에 포함 여부를 확인할 때는 포함연산자와 `any`, `all`, `none`을 사용하면 됩니다.

 

포함 연산자는 아래와 같습니다.

 

연산자 의미 표현식 내부적 실행
in 포함 b in a a.contains(b)
!in 포함하지 않음 b !in a !a.contains(b)

`!` 는 부정연산자로 피연산자의 결과의 반대를 출력합니다.

 

 

'any'는 하나라도 해당되면 참을 반환합니다.

'all'은 모든 것이 해당되어야 참을 반환합니다.

'none'은 모든 것이 해당되지 않아야 참을 반환합니다.

 

이를 사용한 예시는 아래와 같습니다.

fun main() {
    val numbers = listOf(1, 2, 3, 4, 5)

    val num = 3
    println(num in numbers) // Output: true

    println(numbers.any { it == num }) // Output: true
    println(numbers.all { it == num }) // Output: false
    println(numbers.none { it == num }) // Output: false
}

 

 

논리연산자

비교 연산자를 사용한 결과를 묶어서 다시 판단할 수 있는 연산자가 논리연산자입니다.

 

연산자 의미 표현식 상응하는 메서드
|| 두 조건식이 전부 거짓인 경우만 거짓 (a > b) || (a < c) (a > b) or (a < c)
&& 두 조건식이 전부 참인 경우만 참 (a > b) && (a < c) (a > b) and (a < c)

 

아래는 논리연산자를 사용한 간단한 예입니다.

fun main() {
    val numbers = listOf(1, 2, 3, 4, 5)

    val num = 3
    println(num in numbers) // 출력: true

    val otherNum = 6
    println(num == otherNum || num in numbers) // 출력: true (num in numbers가 true이므로)
    println(num == otherNum && num in numbers) // 출력: false (num in numbers는 true이지만, num == otherNum은 false이므로)
}

 

 

동등성

위에서 본 비교연산자인 `==` 연산자는 두 값이 서로 같은지를 비교하는 데 사용되는 연산자입니다. `==` 연산자는 두 값의 구조적 동등성을 비교합니다. 그런데 두 값을 비교하는 것에 그치지 않고 두 값의 객체가 정확히 동일한 객체인지를 비교하고 싶을 수도 있습니다. 이를 위한 참조적 동등성을 비교하는 연산자가 바로 `===` 연산자 입니다.

 


구조적 동등성(Structural Equality): 동일한 값을 비교 `==`
참조적 동등성(Referential Equality): 객체 참조로 비교 `===`

 

간단히 예를 들어보겠습니다.

data class Person(val name: String, val age: Int)

fun main() {
    val person1 = Person("준형", 26)
    val person2 = Person("준형", 26)
    val person3 = person1

    println(person1 === person2) // 출력: false (person1과 person2는 다른 객체이므로 false)
    println(person1 == person2) // 출력: true (person1과 person2의 값이 같음으로 true)
    println(person1 === person3) // 출력: true (person1과 person3은 같은 객체이므로 true)
}

 

`Person` 클래스의 두 인스턴스 person1과 person2는 동일한 내용을 가지고 있지만 서로 다른 객체입니다. 따라서 참조적 동등성을 비교하면 false가 나오고 구조적 동등성을 비교하면 true가 나오게 됩니다.

 


위의 예시에서 사용한 data class는 7장에서 자세히 공부하기에 간단하게만 설명하자면 데이터를 저장하고 전달하기 위해 사용되는 데이터를 다루는데 특화된 클래스입니다. 데이터 클래스에는 여러 가지 멤버 함수가 있는데 그중 `equals` 함수가 `==` 연산자를 사용했을 때 두 객체의 구조적 동등성을 비교한 결과를 반환해 주게 됩니다. 기본 클래스에는 이런 기능에 없기에 `==`를 연산자를 사용해도 구조적 동등성을 비교할 수는 없습니다.

 

 

 

조건문


kotlin에서 조건을 처리할 때는 흐름 제어 구문인 ifwhen을 사용할 수 있습니다.

 

if

단순 조건은 if만 사용하거나 if else로 작성할 수 있습니다.

 

여러 문장을 처리하려면 코드 블록(중괄호)를 사용하면 됩니다. 또한 if 문의 결과를 변수에 할당할 수도 있습니다. 코드 블록의 마지막 라인에 할당할 값을 표시하면 결과가 변수에 할당됩니다.

fun main() {
    val score = 80
    var pass = 0
    var fail = 0

    val result = if (score >= 70) {
        pass++
        "합격"
    } else {
        fail++
        "불합격"
    }

    println(result) // 출력: 합격
    println(pass) // 출력: 1
    println(fail) // 출력: 0
}

 

조건문 내부에 조건문을 작성할 수도 있습니다. 아래의 코드는 조건문 내부에 pass한 사람의 인원이 3명을 초과할 경우 "인원수 초과"로 불합격 처리합니다.

fun main() {
    val score = 80
    var pass = 3
    var fail = 0

    val result = if (score >= 70) {
        pass++
        if (pass < 3) {
            "합격"
        } else {
            "불합격(인원수 초과)"
        }
    } else {
        fail++
        "불합격"
    }

    println(result) // 출력: 불합격(인원수 초과)
    println(pass) // 출력: 4
    println(fail) // 출력: 0
}

 

 

when

when 문은 자바의 switch 문과 유사합니다. when 문도 if 문과 마찬가지로 결과를 변수에 할당할 수 있습니다.

 

한번 if 문으로 정수가 주어졌을 때 변수에 해당 정수에 해당하는 요일을 문자열로 할당하는 코드를 작성해 보겠습니다.

fun main() {
    val day = 6
    val result = if (day == 1) {
        "일요일"
    } else if (day == 2) {
        "월요일"
    } else if (day == 3) {
        "화요일"
    } else if (day == 4) {
        "수요일"
    } else if (day == 5) {
        "목요일"
    } else if (day == 6) {
        "금요일"
    } else if (day == 7) {
        "토요일"
    } else {
        "잘못된 입력"
    }

    println(result) // 출력: 금요일
}

 

if 문과 when 문의 기능은 모든 경우에 동일한 기능을 수행할 수 있습니다. 죽, when 문을 사용하여 할 수 있는 작업은 if 문을 사용하여 대체할 수 있으며 그 반대도 성립합니다. 그렇다면 위의 if 문을 when 문으로 나타내 보겠습니다.

fun main() {
    val day = 6
    val result = when (day) {
        1 -> "일요일"
        2 -> "월요일"
        3 -> "화요일"
        4 -> "수요일"
        5 -> "목요일"
        6 -> "금요일"
        7 -> "토요일"
        else -> "잘못된 입력"
    }

    println(result) // 출력: 금요일
}

 

when 문으로 나타낸 코드를 보면 알 수 있는건 가독성이 매우 좋아졌다는 것입니다. 이렇듯 when 문은 다양한 패턴과 범위를 지원하여 복잡한 다중 조건 로직을 더 간결하게 표현할 수 있습니다. 다만 when 문에서는 else를 추가해야 합니다

 


.when 문에서 else를 생략할 수 있는 경우도 있습니다. 가능한 모든 경우를 처리하는 경우에 else를 생략할 수 있는데 이 부분은 나중에 배우기에 생략하도록 하겠습니다.

 

 

예외

프로그램이 정상적으로 처리할 수 없는 상황이 발생하면 예외(Exception)가 발생합니다. 예외는 실행 시 발생할 수 있는 오류를 나타내는 매커니즘입니다. 다만 예외가 발생하면 프로그램이 비정상적으로 종료될 수 있습니다. 이를 방지하고 예방하려면 예외 처리를 해야합니다.

 

코틀린은 자바와 마찬가지로 `try`, `catch`, `finally` 블록을 사용합니다. 예외는 `throw`를 통해 던질 수 있습니다. throw로 예외를 던지면 이를 catch로 예외를 받아 처리합니다.

 

아래의 코드를 실행해 보겠습니다.

fun main() {
    val a = 10
    val b = 0

    println(a / b)
}

 

위의 코드에는 문제가 있습니다. 0으로는 숫자를 나눌 수 없는데 b가 0입니다. 이 코드를 실행하면 다음과 같은 오류가 발생하면서 프로그램이 강제로 종료됩니다.

Exception in thread "main" java.lang.ArithmeticException: / by zero

 

우리는 프로그램이 종료되는 것을 원하지 않습니다. 이제 예외 처리를 한 코드를 보겠습니다.

fun main() {
    val a = 10
    val b = 0

    try {
        if (b == 0)
            throw Exception("0으로 나누는 것은 불가능합니다.")
        
        println(a / b)
    } catch (e: Exception) {
        println("Exception caught: ${e.message}")
    } finally {
        println("종료")
    }
}

 

위의 코드에서 똑같이 a에 b를 나누는 코드를 실행하고 있지만 b가 0일 때는 `throw Exception` 을 통해 예외를 던지고 있습니다. 이제 위의 코드를 실행한 결과를 보겠습니다.

Exception caught: 0으로 나누는 것은 불가능합니다.
종료

 

try 내에는 예외가 발생할 지점을 정의합니다. 그리고 예외가 발생하면 catch가 이를 처리합니다. finally는 결과와 상관없이 항상 실행됩니다.

 

 

Exception 클래스

Exception 클래스를 좀 더 자세히 알아보겠습니다.

 

코틀린과 자바에서 모두 Exception 클래스는 Throwable 클래스를 상속하고 있습니다. Exception 클래스는 checked exception과 unchecked exception으로 나눌 수 있습니다.

 

 1. Checked Exception: Checked Exception은 프로그램 실행 시 반드시 처리해야 하는 예외입니다. 이러한 예외들은 명시적으로 예외 처리 코드를 작성하지 않으면 컴파일 오류가 발생합니다. IOException, SQLException 등이 일반적인 Checked Exception입니다.

 2. Unchecked Exception: Unchecked Exception은 명시적인 예외 처리를 강제하지 않습니다. 주로 프로그래밍 오류에 의해 발생하는 예외이며, RuntimeException 클래스와 그 하위 클래스들이 해당됩니다. 예를 들어, ArithmeticException, NullPointerException 등이 Unchecked Exception에 속합니다.

 

일반적으로 코틀린에서 사용되는 기본 예외 클래스는 아래와 같습니다.

예외  클래스 설명
Exception 모든 예외 클래스의 기본 클래스
ArithmeticException 산술 연산 오류 (예: 0으로 나누기)
ArrayIndexOutOfBoundsException 배열 인덱스 범위를 벗어난 오류
ClassCastException 객체의 형 변환이 잘못된 경우 발생하는 오류
IllegalArgumentException 잘못된 인수가 전달된 오류
IllegalStateException 메서드 호출이 잘못된 상태에서 수행되는 오류
NullPointerException null 참조로 인한 오류
IndexOutOfBoundsException 인덱스 범위를 벗어난 오류
NoSuchMethodException 호출한 메서드가 존재하지 않는 경우 발생하는 오류
NoSuchFieldException 찾으려는 필드가 존재하지 않는 경우 발생하는 오류
UnsupportedOperationException 호출한 메서드가 지원되지 않는 경우 발생하는 오류
ConcurrentModificationException 동시 수정이 발생한 경우 발생하는 오류
SecurityException 보안 관련 문제로 인해 발생하는 오류
OutOfMemoryError 메모리 부족으로 인한 오류
StackOverflowError 스택 오버플로우로 인한 오류
NumberFormatException 숫자로 변환할 수 없는 문자열을 변환하려고 할 때 발생하는 오류
InterruptedException 스레드가 중단되었을 때 발생하는 오류
IOException 입출력 작업 시 발생하는 오류
FileNotFoundException 파일을 찾을 수 없는 오류

 

위에서 예를 들었던 코드에선 기본 클래스를 사용해서 예외 처리를 하고 있었습니다. 산술 연산 오류를 처리하는 예외 클래스가 따로 있기에 이를 사용하는 것이 더 좋습니다.

fun main() {
    val a = 10
    val b = 0

    try {
        if (b == 0)
            throw ArithmeticException("0으로 나누는 것은 불가능합니다.")
        
        println(a / b)
    } catch (e: ArithmeticException) {
        println("ArithmeticException caught: ${e.message}")
    } finally {
        println("종료")
    }
}

 

 

 

순환 표현 알아보기


범위

숫자나 문자 등을 연속적인 특정 영역으로 지정할 수 있는데 이를 범위(Range)라고 합니다. 범위를 나타낼 수 있는 연산자를 범위 / 진행연산자라 하며 이는 다음과 같습니다.

 

연산자 표현식 설명
범위연산자 (..) a <= x <= b 두 수, 두 문자, 두 문자열 사이에 지정해서 두 항목을 포함한 범위 객체를 생성
rangeTo a <= x <= b 범위 연산자와 동일
until a <= x < b 범위 연산자와 동일하지만 마지막 항목은 포함하지 않음
downTo b >= x => a 범위 연산자와 동일하지만 역방향을 나타낸다
step (a..b).step(x) 범위의 간격을 처리하는 메서드
first, last, step 속성   범위와 진행 내의 첫 번째, 마지막, 간격 정보를 관리하는 속성

 

다음은 범위 / 진행연산자를 사용한 예시입니다.

fun main() {
    // 범위 연산자 (..) 사용
    val range1 = 1..5 // 1부터 5까지의 범위 생성 (1, 2, 3, 4, 5)

    // rangeTo 함수 사용
    val range2 = 10.rangeTo(20) // 10부터 20까지의 범위 생성 (10, 11, ..., 20)

    // until 함수 사용
    val range3 = 1.until(10) // 1부터 9까지의 범위 생성 (1, 2, 3, ..., 9)

    // downTo 함수 사용
    val range4 = 5.downTo(1) // 5부터 1까지의 역순 범위 생성 (5, 4, 3, 2, 1)

    // step 함수 사용
    val range5 = 1..10 // 1부터 10까지의 범위 생성
    val stepRange = range5.step(2) // 2 간격으로 순회하는 범위 생성 (1, 3, 5, 7, 9)

    // (first, last, step) 속성 사용
    val range6 = 10..20 // 10부터 20까지의 범위 생성
    for (num in range6.first..range6.last step 2) {
        print("$num ")
    } // 출력: 10 12 14 16 18 20
}

 

 

for 순환

순환(loop)는 동일한 코드를 반복해서 처리하는 규칙을 제공하는 문장입니다.

fun main() {
    for (num in 1..10) {
        print("$num ")
    } // 출력: 1 2 3 4 5 6 7 8 9 10
}

 

위는 for을 이용해 1부터 10까지의 숫자를 출력한 코드입니다. for으로 시작하고 순환 조건은 소괄호 사이에 지정합니다. 순환 조건은 in을 기준으로 앞에는 지역변수를 지정하고 뒤에는 반복형(Iterable), 즉 컬렉션 클래스의 객체인 범위, 배열 집합, 맵 등이 들어갑니다.

 


반복형(Iterable)이란?
Iterable은 반복 가능한 컬렉션을 나타내는 인터페이스입니다. Iterable은 일련의 요소들을 순회하고, 각 요소에 접근하여 작업을 수행할 수 있는 기능을 제공합니다. koltin에는 `Iterable` 인터페이스를 구현하는 다양한 컬렉션 타입들이 있습니다. 예, List, Set, Map, Array, Sequence

 

 

다음은 for 순환을 사용한 다양한 예시입니다.

fun main() {
    for (i in 1..5) {
        print("$i ")
    } // 출력: 1 2 3 4 5
    println()

    for (i in 1.rangeTo(5)) {
        print("$i ")
    } // 출력: 1 2 3 4 5
    println()

    for (i in 1 until 5) {
        print("$i ")
    } // 출력: 1 2 3 4 5
    println()

    for (i in 1.until(5)) {
        print("$i ")
    } // 출력: 1 2 3 4
    println()

    for (i in 1..1) {
        print("$i ")
    } // 출력: 1 2 3 4
    println()

    for (i in 2..1) {
        print("$i ")
    } // 출력: 아무것도 출력되지 않음
    println()

    for (i in 1..5 step 2) {
        print("$i ")
    } // 출력: 1 3 5
    println()

    for (i in 5 downTo 1) {
        print("$i ")
    } // 출력: 5 4 3 2 1 
    println()

    for (i in 5.downTo(1)) {
        print("$i ")
    } // 출력: 5 4 3 2 1 
    println()
}

 

 

for 순환 중단 처리

순환을 처리할 때 중단하거나 특정 조건을 만족해서 더 이상 순환을 할 필요가 없을 경우 현재 처리를 중단하거나 다음 순환으로 넘어갈 수 있는 방법이 있습니다.

 

break - 순환 내부에서 특정 로직을 만나면 현재의 순환을 바로 중단한다.

continue - 순환 내부에서 특정 로직을 만나면 다음 반복으로 이동한다.

 

이를 사용한 예는 아래와 같습니다.

for (i in 1..5) {
    if (i == 2) {
        continue  // 현재 루프를 건너뜀
    }
    if (i == 4) {
        break  // 루프를 완전히 종료
    }
    println("현재 숫자: $i")
}

// 출력
// 현재 숫자: 1
// 현재 숫자: 3

 

순환문 내부에 순환문이 있을 경우 break와 continue 만으로 전체 순환문을 종료하긴 힘듭니다.  그래서 이 때는 레이블(label)을 지정해야 합니다. 레이블은 특정 루프에 이름을 붙이는 데 사용되는 식별자입니다. 

 

다음은 break@와 continue@를 사용한 예시입니다.

fun main() {
    println("중첩된 레이블과 break@ 예시")
    outerLoop@ for (i in 1..3) {
        for (j in 1..3) {
            if (i == 2 && j == 2) {
                break@outerLoop  // 바깥쪽 루프를 종료
            }
            println("i: $i, j: $j")
        }
    }

    println("중첩된 레이블과 continue@ 예시")
    outerLoop@ for (i in 1..3) {
        for (j in 1..3) {
            if (i == 2 && j == 2) {
                continue@outerLoop  // 바깥쪽 루프의 다음 반복으로 이동
            }
            println("i: $i, j: $j")
        }
    }
}

// 출력
// 중첩된 레이블과 break@ 예시
// i: 1, j: 1
// i: 1, j: 2
// i: 1, j: 3
// i: 2, j: 1
// 중첩된 레이블과 continue@ 예시
// i: 1, j: 1
// i: 1, j: 2
// i: 1, j: 3
// i: 2, j: 1
// i: 3, j: 1
// i: 3, j: 2
//i: 3, j: 3

 

 

while 순환

while문은 소괄호 안에 조건식을 넣고 해당 조건을 만족하지 않을 때까지 반복문을 진행합니다.

fun main() {
    var count = 1

    // while 반복문
    while (count <= 5) {
        print("$count ") // 출력: 1 2 3 4 5
        count++
    }
}

 

 

do while 순환

do while 문은 한 번 실행한 후에 조건을 확인하고 참일 경우에 순환합니다.

fun main() {
    var count = 1

    // do while 반복문
    do {
        print("$count ") // 출력: 1 2 3 4 5
        count++
    } while (count <= 5)
}

 

while은 반복하기 전에 조건을 체크하고 do while은 반복 이후에 조건을 체크한다는 점에서 차이가 있습니다.

 

 

반복자(Iterator)

위에서 간단하게 Iterable에 대해서 알아보았습니다. 범위, 배열, 리스트 등은 Iterable 인터페이스를 구현한 클래스이며 이를 객체로 만들어도 Iterable한 객체가 됩니다. `iterator()` 메서드를 사용하면 Iterator 클래스의 객체로 변환할 수 있습니다. 

 

Iterable과 Iterator의 차이는 다음과 같습니다. Iterable 인터페이스는 순회 가능한 컬렉션을 나타내는 인터페이스입니다. Iterator 인터페이스는 컬렉션의 요소를 하나씩 순회하기 위한 인터페이스입니다. 

 

Iterable 인터페이스를 구현하는 클래스는 iterator() 메서드를 정의하여 해당 컬렉션의 요소에 순차적으로 접근할 수 있는 Iterator 객체를 반환하도록 구현해야 합니다. 하지만 기본적으로 Kotlin의 표준 라이브러리에서 제공하는 컬렉션들은 Iterable 인터페이스를 이미 구현하여 제공하므로 사용자가 직접 구현할 필요가 없습니다.

 

아래의 리스트에 iterator() 메서드가 구현되어 있는지 한번 확인해보겠습니다. 

val list : List<Int>

 

아래는 kotlin 표준 라이브러리의 Collections.kt 에 있는 List 인터페이스의 일부분입니다.

public interface List<out E> : Collection<E> {
    // Query Operations

    override val size: Int
    override fun isEmpty(): Boolean
    override fun contains(element: @UnsafeVariance E): Boolean
    override fun iterator(): Iterator<E>

    ...
}

 

iterator 메서드가 잘 구현이 되어있는 것을 확인할 수 있습니다.

 

iterator 메서드는 Iterator 인터페이스를 상속하고 있는데 이것도 한번 보겠습니다.

public interface Iterator<out T> {
    /**
     * Returns the next element in the iteration.
     */
    public operator fun next(): T

    /**
     * Returns `true` if the iteration has more elements.
     */
    public operator fun hasNext(): Boolean
}

 

Iterator 인터페이스에는 `next()`와 `hasNext()`라는 2개의 메서드가 있습니다.

 

next() 메서드는 컬렉션의 다음 요소를 반환하며, 컬렉션을 순회할 때마다 호출됩니다.

hasNext() 메서드는 다음 요소가 있는지 여부를 확인합니다. 더 이상 컬렉션에 다음 요소가 없는 경우 false를 반환합니다.

 

Iterator를 사용한 예시는 다음과 같습니다.

fun main() {
    val numbers = listOf(1, 2, 3, 4, 5)

    // Iterator를 통해 리스트의 요소 순회
    val iterator = numbers.iterator()
    while (iterator.hasNext()) {
        val number = iterator.next()
        print("$number ") // 출력: 1 2 3 4 5
    }
}
profile

Developing Myself Everyday

@배준형

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!