Kotlin in Action 을 공부하고 Effective kotlin 의 내용을 조금 참조하여 정리한 글입니다.
while 루프
코틀린에는 자바와 같은 문법의 `while` 과 `do-while` 루프가 있다.
while (조건) { // 조건이 참인 동안 본문을 반복 실행
/* ... */
}
do {
/* ... */
} while (조건) // 맨 처음에 무조건 본문을 한 번 실행, 그 후 조건이 참인 동안 본문을 반복 실행
수에 대한 iteration: range(범위), progression(수열)
자바에서는 `for` 루프를 아래처럼 사용한다.
`for (어떤 변수 i 를 초기화하는 식; 각 루프 후 조건 검사 ;변수 i를 루프를 한번 실행할 때마다 갱신)`
코틀린에서는 `for` 루프를 위처럼을 사용할 수 없다.
하지만 이런 루프를 가장 흔히 사용하는 초기값, 증가값, 최종값을 사용한 루프를 대신하기 위해 코틀린에서는 range(범위)를 사용한다.
코틀린의 `for` 문은 C# 처럼 `for (<아이템> in <원소들>)` 형태를 취한다.
범위는 아래처럼 사용한다.
`val oneToTen = 1 .. 10`
코틀린에서 범위는 양 쪽 모두 닫힌 구간이다. 즉, `1 .. 10` 은 1과 10 이 포함된다.
어떤 범위에 속한 값을 일정 순서로 iteration 하는 경우를 progression(수열) 이라고 한다.
`when` 을 이용해 Fizz-Buzz 게임을 구현하고 범위를 사용한 반복문으로 그 게임을 사용해보자.
3 으로 나누어 떨어지는 수는 Fizz, 5 로 나누어 떨어지는 수는 Buzz, 둘 모두 나누어 떨어지는 수는 FizzBuzz 가 되는 것이다.
fun fizzBuzz(i: Int) = when {
i % 15 == 0 -> "FizzBuzz "
i % 3 == 0 -> "Fizz "
i % 5 == 0 -> "Buzz "
else -> "$i "
}
증가 값 `step` 을 갖는 수열에 대해 iteration 하도록 만들 수도 있다.
`downTo` 라는 키워드를 통해 역방향 수열을 만들 수도 있다. `100 downTo 1` 은 역방향 수열을 만들며 이 때는 기본 `step` 은 -1 이다.
만약 `100 downTo 1 step 2` 라고 하면 증가값의 절대값은 2가 되며, 실제 증가값은 -2 가 된다.
`..` 는 항상 닫힌 범위이다. 만약 오른쪽값을 포함하지 않도록 하려면 `until` 을 사용하면 된다.
`for (x in 0 until size)` 처럼 사용하면 된다. 이는 `for (x in 0 .. size-1)` 과 같은 의미이다.
map 에 대한 iteration
코틀린에서도 컬렉션에 대한 이터레이션을 위해 `for .. in ... ` 루프를 사용하며 자바와 똑같이 동작한다.
`map` 에 대한 `iteration` 을 보자.
fun mapIter() {
val binaryReps = TreeMap<Char, String>() // 키에 대해 정렬하기 위해 TreeMap 사용
for (c in 'A'..'F') { // A 부터 F 까지 문자의 범위를 사용해 iteration
val binary = Integer.toBinaryString(c.code) // ASCII 코드를 2진 표현으로 바꿈
binaryReps[c] = binary
}
for ((letter, binary) in binaryReps) { // map 에 대해 iteration
println("$letter: $binary")
}
}
위 메서드는 문자에 대해 이진 코드를 출력하는 메서드이다.
결과:
A: 1000001
B: 1000010
C: 1000011
D: 1000100
E: 1000101
F: 1000110
`..` 연산자는 숫자 타입 뿐 아니라 문자 타입의 값에도 적용할 수 있다.
또한 `binaryReps[c] = binary` 라는 코틀린 코드는 `binaryReps.put(c, binary)` 라는 자바 코드와 같다.
위에서 `(letter, binary) in binaryReps)` 라는 형태로 구조 분해 구문을 사용했던 것처럼
`map` 이 아닌 다른 컬렉션에도 활용할 수 있다.
fun listIter() {
val list = arrayListOf("10", "11", "1001")
for ((index, element) in list.withIndex()) { // 인덱스와 함께 컬렉션을 iteration
println("$index: $element")
}
}
위처럼 사용하면, 인덱스를 저장하기 위한 변수를 별도로 선언하고 루프에서 매번 그 변수를 증가시킬 필요가 없다.
결과
0: 10
1: 11
2: 1001
in 으로 컬렉션, 범위의 원소 검사하기
`in` 연산자를 사용해서 어떤 값이 범위에 속하는지 검사하고, `!in` 을 사용하여 어떤 값이 범위에 속하지 않는지 검사할 수 있다.
fun isLetter(c: Char) = c in 'a'..'z' || c in 'A'..'Z'
fun isNotDigit(c: Char) = c !in '0'..'9'
println(isLetter('q'))
// true
println(isNotDigit('x'))
// true
`c in 'a' .. 'z'` 코드는 실제로는 비교 로직 ` 'a' <= c && c <= 'z'` 로 변환된다.
실제 비교 로직은 표준 라이브러리의 범위 클래스 구현 안에 깔끔히 감춰져 있다.
`in` 은 `when` 식에서 사용해도 된다.
fun recognize(c: Char) = when (c) {
in '0'..'9' -> "It's a digit!"
in 'a'..'z', in 'A'..'Z' -> "It's a letter!"
else -> "I don't know"
}
println(recognize('8'))
// It's a digit!
범위는 문자에만 국한되지 않으며 비교가 가능한 클래스라면(`java.lang.Comparable` 인터페이스를 구현한 클래스라면) 그 클래스의 인스턴스 객체를 사용해 범위를 만들 수 있다.
`Comparable` 을 사용하는 범위의 경우, 그 범위 내의 모든 객체를 항상 이터레이션하지는 못한다.
('Java' 와 'Kotlin' 사이의 모든 문자열을 이터레이션할 수 없음)
하지만 `in` 연산자를 사용하여 값이 범위 안에 속하는지는 항상 결정할 수 있다.
println("Kotlin" in "Java" .. "Scala")
// true
위 `in` 식은 `"Java" <= "Kotlin" && "Kotlin" <= "Scala"`와 같다.
`String` 의 `Comparable` 구현에서는 두 문자열을 알파벳 순서로 비교하기 때문에 `in` 검사에서도 마찬가지로 문자열을 알파벳 순서로 비교한다.
컬렉션에도 마찬가지로 `in` 연산을 사용할 수 있다.
'Kotlin' 카테고리의 다른 글
[Kotlin] 컬렉션에서의 함수 정의와 호출 (2) | 2024.01.01 |
---|---|
[Kotlin] 예외 처리 (1) | 2023.12.28 |
[Kotlin] enum & when & smart cast(스마트 캐스트) (1) | 2023.12.27 |
[Kotlin] 클래스와 프로퍼티 기초 (0) | 2023.12.27 |
[Kotlin] 함수와 변수 기초 (1) | 2023.12.02 |