본문 바로가기
프로그래밍/코틀린

K009. 코틀린 루프(반복분, Loop) 깊게 살펴보기 - 반복자(Iterators)

by K-인사이터 2024. 2. 4.
반응형

안녕하세요

K-IN 입니다.

 

코틀린 반복자(Iterator)에 대해서 알아보겠습니다. 

전체 강의 목록은 아래의 링크를 클릭해주세요.

 

K000. 코틀린 시리즈 (연재물)

안녕하세요 K-IN 입니다. 요즘 코틀린을 이용한 개발 프로젝트가 늘어나고 있습니다. 이에, 코틀린에 대해서 상세하게 정리하는 간행물을 제작하고자 합니다. 여기에 있는 링크들은 모두 코틀린

k-in.tistory.com

 

 

코틀린 루프(반복문, Iterators) 

코틀린은 루프(반복문)을 위한 다양한 방법을 제공합니다. 

  • for 
  • while
  • do-while
  • Iterators

각각의 특징을 살펴보면서 문법에 익숙해지는 시간을 가지겠습니다. 

코틀린 루프(반복문): Iterators

Java 처럼 코틀린(Kotlin) 또한 컬렉션 유형의 타입들이 존재합니다.

이러한 컬렉션들은 여러개의 요소(elements)들을 보관하고 관리하는데 필요한 타입니다. 

 

가령 인사관리(HR)를 위해 직원(Employee)들의 정보를 선언하는 클래스와 객체들이 있다고 가정해볼까요.

Employee 클래스가 선언되고 Employee 객체들이 초기화될 것입니다. 

이때, 직원들의 수가 3000명이 넘는다고 할때 직원 검색 페이지를 개발한다면 이들을 효과적으로 조회, 출력할 방법이 필요합니다. 

이를 위해서 객체들을 보관할 컬렉션(Collection)이 필요하게됩니다. 

 

이제 문제는 컬렉션 객체에서 어떻게 정보를 찾아내느냐 입니다.

이때 사용되는 방법이 바로 반복자(Iterator)입니다. 

아래의 코드 예시를 보면서 이해하면 편리합니다. 

val numbers = listOf("one", "two", "three", "four")
val numbersIterator = numbers.iterator()
while (numbersIterator.hasNext()) {
    println(numbersIterator.next())
}

 

샘플 코드에서 listOf 라는 새로운 함수가 사용되었습니다. 코틀린 표준라이브러리에서 사용되는 함수입니다. 

간단히 특징을 파악해봅니다. 

 

▷ listOf (공식문서)

  • listOf 는 주어진 요소들을 위한 읽기 전용(read-only) 리스트(list)를 리턴합니다.
  • 면접 질문 예상 ➡️ 리턴되는 리스트는 직렬화됩니다. (JVM 에서만, JS 에서는 그렇지 않음)

샘플 코드를 통해 반복자(iterator, 이하 반복자)의 코드 패턴을 눈으로 익혔습니다.

반복자(Iterator)의 특징 핵심 요약 

반복자의 특징을 나열해서 살펴보겠습니다. 

  • 반복자는 컬렉션의 기본 구조를 드려내지 않고 요소에 순차적으로 접근을 제공하는 객체로 정의할 수 있습니다. 

여기서 컬렉션의 기본 구조를 드러내지 않는다는 말은 개발자가 컬렉션 요소들을 순회하는데만 집중할 수 있도록 인터페이스를 제공해준다는 의미입니다. 

  • 반복자는 컬렉션의 요소들의 값을 출력하거나 일괄적으로 업데이트를 할 때 유용합니다. 

예를 들어 윤석열 정부에 들어 대한민국 나이체계가 바뀌었습니다. 따라서 전직원의 나이 정보를 업데이트하는 작업이 필요합니다. 

이때, 일괄적으로 나이를 조정하기 위해 반복자를 사용하게 됩니다.

 

면접 질문 예상 ➡️ 다음으로는 반복자를 사용하기 위해 꼭 기억해야 하는 내용들입니다. 

  • Iterator<T> 인터페이스를 상속하는 클래스들의 iterator() 함수를 호출함으로써 반복자를 얻을 수 있습니다.
  • List 타입과 Set 타입은 Iterator<T> 인터페이스를 이미 상속하므로 iterator() 함수를 호출하여 반복자를 얻을 수 있습니다. 
  • iterator() 함수를 통해 리턴된 반복자는 컬렉션의 첫번째 요소(element)를 가리키고 있습니다. 
  • next() 함수를 호출 시 가리키는 첫번째 요소를 리턴하고 다음 요소가 있다면 반복자는 다음요소를 가리킵니다. 
  • haxNext() 함수를 통해 다음 요소가 존재하는지 확인 가능하며 일반적으로 while 조건문에 사용됩니다. 마지막 요소까지 순회를 하였다면 hasNext() 함수는 False 를 리턴하면서 while 루프가 종료됩니다. 
  • 반복자가 마지막 위치를 가리킨다면 다시 처음 위치로 돌아올 수 없습니다. 따라서 재 순회를 하려면 새로운 반복자를 생성해야합니다. 

암시적으로 반복자(Iterator)를 이용하는 코드 패턴 

※ 여기서 암시적(implicitly)의 의미는 표면적인 코드 상으로는 보여지는 것과 다르게 내부적으로 동작함을 말합니다. 

 

코틀린은 암시적으로 동작하는 경우가 더러 있습니다. 따라서, 일반적인 for 루프도 컬렉션에 대해서는 암시적으로 반복자(iterator)를 사용하기도 합니다. 

 

즉, 다음과 같은 코드는 반복자를 암시적으로 사용합니다. 

val numbers = listOf("one", "two", "three", "four")
for (item in numbers) {
    println(item)
}

 

또한, 컬렉션 클래스들이 제공하는 forEach 함수 또한 암시적으로 반복자를 사용합니다. 

val numbers = listOf("one", "two", "three", "four")
numbers.forEach {
    println(it)
}

 

양방향 반복자(Iterator)를 제공하는 List Iterators 

앞서 반복자는 일회용이고 한 방향으로만 순회가 가능한 것으로 이해했습니다. 

그러나, List 타입에 한하여 양방향(앞으로, 뒤로) 순회할 수 있는 ListIterator 가 있습니다. 

ListIterator 는 코틀린 표준라이브러리에서 제공하는 기능입니다.

공식문서는 "ListIterator 공식문서"링크를 참고하시면 됩니다. 

 

ListIterator 는 매우 편리한 객체입니다.

이 객체가 제공하는 기능을 숙지하면 일반 반복자처럼 양방향 순회 기능을 쉽게 구현할 수 있습니다. 

  • hasPrevious() 함수를 통해 이전 요소가 있는지 확인합니다. 
  • previous() 함수는 실제로 이전 요소를 리턴하고 다음 이전요소로 이동시키는 역할을합니다. 
  • nextIndex() 함수는 다음 요소의 인덱스(순번)을 리턴합니다. 
  • previousIndex() 함수는 이전 요소의 인덱스(순번)을 리턴합니다. 

예시 코드는 다음과 같습니다. 

  • 이 예시에서 포인트는 생성한 반복자를 마지막 요소까지 순회를 시킨 후에 역방향으로 순회를 한다는 점입니다. 

그리고 처음에 리턴한 반복자를 계속 사용하고 있습니다. 

val numbers = listOf("one", "two", "three", "four")
val listIterator = numbers.listIterator()
while (listIterator.hasNext()) listIterator.next()
println("Iterating backwards:")
while (listIterator.hasPrevious()) {
    print("Index: ${listIterator.previousIndex()}")
    println(", value: ${listIterator.previous()}")
}

 

따라서, 샘플 코드의 출력 결과는 다음과 같습니다. 

Iterating backwards:
Index: 3, value: four
Index: 2, value: three
Index: 1, value: two
Index: 0, value: one

 

변경이 가능한 반복자(Iterator)를 제공하는 Mutable iterators 

앞서 arrayOf 는 read-only 의 리스트 컬렉션을 반환한다고 배웠습니다. 

 

만약 컬렉션의 성질이 다르다면 반복자가 단순 출력과 순회가 아닌 요소(element)를 삭제하는 등

컬렉션을 변경하는 일 또한 가능합니다. 

 

mutableListOf 함수를 통해 컬렉션을 생성하고 반복자(iterator)를 생성하면

이 반복자는 요소(element)를 컬렉션으로부터 제거가 가능합니다. 

 

뮤터블(mutable) 컬렉션은 Iterator 를 확장한 MutableIterator 를 구현하고 있으며

remove 함수를 통해 요소(element)를 컬렉션에서 제거할 수 있습니다. 

 

따라서, 컬렉션을 출력 시 퇴사처리된 직원들을 제외하고 출력할 수 있게됩니다. 

다음의 코드는 뮤터블 컬렉션에서 반복자를 리턴받고 요소를 삭제하는 코드입니다. 

val numbers = mutableListOf("one", "two", "three", "four") 
val mutableIterator = numbers.iterator()

mutableIterator.next()
mutableIterator.remove()    
println("After removal: $numbers")

 

실행하게되면 "one" 요소가 삭제되어 출력됩니다. 

After removal: [two, three, four]

 

또한, 다른 계열사의 직원들을 출력하는 작업이 필요할 수 있습니다. 

이때 동일한 데이터베이스가 아닌 계열사 HR 시스템에서 RESTFul API 를 통해서 데이터를 가져온다고 가정하겠습니다. 

이때, MutableListIterator 를 사용하면 새로운 요소를 추가(insert)하거나 기존 요소를 대체(replacement) 할 수 있습니다. 

 

다음의 코드는 "two" 요소를 추가하고 중복된 "four" 요소들 중 하나를 "three" 요소도 대체(set 함수)합니다. 

val numbers = mutableListOf("one", "four", "four") 
val mutableListIterator = numbers.listIterator()

mutableListIterator.next()
mutableListIterator.add("two")
mutableListIterator.next()
mutableListIterator.set("three")   
println(numbers)

 

실행하게되면 다음과 같습니다. 

[one, two, three, four]

 

지금까지 배운 개념들을 플레이그라운드에서 연습하면서 오늘 배웠던 내용을 복습합니다. 

 

 

이상입니다.

K-IN 올림. 

반응형